@lix-js/sdk 0.6.0-preview.4 → 0.6.0-preview.5
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/README.md +1 -1
- package/SKILL.md +65 -64
- package/dist/engine-wasm/index.js +4 -4
- package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
- package/dist/engine-wasm/wasm/lix_engine.js +130 -118
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -8
- package/dist/generated/builtin-schemas.d.ts +69 -69
- package/dist/generated/builtin-schemas.js +94 -94
- package/dist/open-lix.d.ts +33 -26
- package/dist/open-lix.js +10 -10
- package/dist/sqlite/index.js +86 -30
- package/dist-engine-src/README.md +3 -3
- package/dist-engine-src/src/backend/capabilities.rs +67 -0
- package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
- package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
- package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
- package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
- package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
- package/dist-engine-src/src/backend/conformance/model.rs +28 -0
- package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
- package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
- package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
- package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
- package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
- package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
- package/dist-engine-src/src/backend/conformance/write.rs +16 -0
- package/dist-engine-src/src/backend/error.rs +94 -0
- package/dist-engine-src/src/backend/in_memory.rs +670 -0
- package/dist-engine-src/src/backend/mod.rs +36 -9
- package/dist-engine-src/src/backend/predicate.rs +80 -0
- package/dist-engine-src/src/backend/traits.rs +260 -0
- package/dist-engine-src/src/backend/types.rs +224 -81
- package/dist-engine-src/src/binary_cas/context.rs +8 -8
- package/dist-engine-src/src/binary_cas/kv.rs +234 -259
- package/dist-engine-src/src/{version → branch}/context.rs +12 -12
- package/dist-engine-src/src/branch/lifecycle.rs +221 -0
- package/dist-engine-src/src/branch/mod.rs +13 -0
- package/dist-engine-src/src/branch/refs.rs +321 -0
- package/dist-engine-src/src/branch/stage_rows.rs +67 -0
- package/dist-engine-src/src/branch/types.rs +21 -0
- package/dist-engine-src/src/catalog/context.rs +18 -18
- package/dist-engine-src/src/catalog/snapshot.rs +8 -8
- package/dist-engine-src/src/changelog/bench_support.rs +785 -0
- package/dist-engine-src/src/changelog/change.rs +1 -0
- package/dist-engine-src/src/changelog/codec.rs +497 -0
- package/dist-engine-src/src/changelog/commit.rs +1 -0
- package/dist-engine-src/src/changelog/context.rs +1614 -0
- package/dist-engine-src/src/changelog/mod.rs +29 -0
- package/dist-engine-src/src/changelog/store.rs +163 -0
- package/dist-engine-src/src/changelog/test_support.rs +54 -0
- package/dist-engine-src/src/changelog/types.rs +213 -0
- package/dist-engine-src/src/commit_graph/context.rs +317 -274
- package/dist-engine-src/src/commit_graph/mod.rs +2 -4
- package/dist-engine-src/src/commit_graph/types.rs +22 -42
- package/dist-engine-src/src/commit_graph/walker.rs +133 -103
- package/dist-engine-src/src/common/error.rs +52 -18
- package/dist-engine-src/src/common/identity.rs +2 -2
- package/dist-engine-src/src/common/mod.rs +1 -1
- package/dist-engine-src/src/domain.rs +42 -46
- package/dist-engine-src/src/engine.rs +74 -96
- package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
- package/dist-engine-src/src/functions/context.rs +56 -52
- package/dist-engine-src/src/functions/state.rs +51 -52
- package/dist-engine-src/src/init.rs +288 -154
- package/dist-engine-src/src/json_store/context.rs +15 -266
- package/dist-engine-src/src/json_store/mod.rs +26 -0
- package/dist-engine-src/src/json_store/store.rs +103 -718
- package/dist-engine-src/src/json_store/types.rs +4 -9
- package/dist-engine-src/src/lib.rs +49 -19
- package/dist-engine-src/src/live_state/context.rs +654 -790
- package/dist-engine-src/src/live_state/mod.rs +9 -3
- package/dist-engine-src/src/live_state/overlay.rs +4 -4
- package/dist-engine-src/src/live_state/types.rs +30 -21
- package/dist-engine-src/src/live_state/visibility.rs +514 -71
- package/dist-engine-src/src/plugin/install.rs +48 -48
- package/dist-engine-src/src/plugin/manifest.rs +7 -7
- package/dist-engine-src/src/plugin/materializer.rs +0 -275
- package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
- package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
- package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
- package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
- package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
- package/dist-engine-src/src/schema/compatibility.rs +11 -11
- package/dist-engine-src/src/schema/definition.json +2 -2
- package/dist-engine-src/src/schema/definition.rs +5 -5
- package/dist-engine-src/src/schema/key.rs +3 -3
- package/dist-engine-src/src/schema/mod.rs +1 -1
- package/dist-engine-src/src/schema/tests.rs +18 -18
- package/dist-engine-src/src/session/context.rs +803 -148
- package/dist-engine-src/src/session/create_branch.rs +94 -0
- package/dist-engine-src/src/session/execute.rs +223 -83
- package/dist-engine-src/src/session/merge/analysis.rs +9 -3
- package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
- package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
- package/dist-engine-src/src/session/merge/mod.rs +5 -6
- package/dist-engine-src/src/session/merge/stats.rs +7 -11
- package/dist-engine-src/src/session/mod.rs +15 -12
- package/dist-engine-src/src/session/switch_branch.rs +113 -0
- package/dist-engine-src/src/session/transaction.rs +495 -14
- package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
- package/dist-engine-src/src/sql2/bind/error.rs +5 -0
- package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
- package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
- package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +71 -3
- package/dist-engine-src/src/sql2/bind/read.rs +65 -0
- package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
- package/dist-engine-src/src/sql2/bind/table.rs +273 -0
- package/dist-engine-src/src/sql2/bind/write.rs +86 -0
- package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
- package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
- package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
- package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
- package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
- package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
- package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
- package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
- package/dist-engine-src/src/sql2/context.rs +36 -30
- package/dist-engine-src/src/sql2/error.rs +1 -1
- package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
- package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
- package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
- package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
- package/dist-engine-src/src/sql2/exec/write.rs +661 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
- package/dist-engine-src/src/sql2/history_projection.rs +8 -8
- package/dist-engine-src/src/sql2/history_route.rs +35 -31
- package/dist-engine-src/src/sql2/mod.rs +28 -23
- package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
- package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
- package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
- package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
- package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
- package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
- package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
- package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
- package/dist-engine-src/src/sql2/plan/write.rs +147 -0
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
- package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
- package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
- package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
- package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
- package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
- package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
- package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
- package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
- package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
- package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
- package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
- package/dist-engine-src/src/sql2/read_only.rs +2 -2
- package/dist-engine-src/src/sql2/session.rs +47 -96
- package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
- package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
- package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
- package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
- package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
- package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
- package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
- package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
- package/dist-engine-src/src/storage/conformance.rs +399 -0
- package/dist-engine-src/src/storage/context.rs +552 -288
- package/dist-engine-src/src/storage/mod.rs +48 -10
- package/dist-engine-src/src/storage/point.rs +440 -0
- package/dist-engine-src/src/storage/read_scope.rs +43 -64
- package/dist-engine-src/src/storage/reader.rs +867 -0
- package/dist-engine-src/src/storage/scan.rs +784 -0
- package/dist-engine-src/src/storage/spaces.rs +236 -0
- package/dist-engine-src/src/storage/stats.rs +80 -0
- package/dist-engine-src/src/storage/write_set.rs +962 -0
- package/dist-engine-src/src/storage_bench.rs +136 -4828
- package/dist-engine-src/src/test_support.rs +360 -138
- package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
- package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
- package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
- package/dist-engine-src/src/tracked_state/context.rs +1927 -993
- package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
- package/dist-engine-src/src/tracked_state/merge.rs +74 -88
- package/dist-engine-src/src/tracked_state/mod.rs +19 -16
- package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
- package/dist-engine-src/src/tracked_state/storage.rs +243 -191
- package/dist-engine-src/src/tracked_state/tree.rs +247 -371
- package/dist-engine-src/src/tracked_state/types.rs +49 -42
- package/dist-engine-src/src/transaction/bench_support.rs +407 -0
- package/dist-engine-src/src/transaction/commit.rs +821 -713
- package/dist-engine-src/src/transaction/context.rs +705 -600
- package/dist-engine-src/src/transaction/mod.rs +13 -2
- package/dist-engine-src/src/transaction/normalization.rs +63 -76
- package/dist-engine-src/src/transaction/prep.rs +13 -13
- package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
- package/dist-engine-src/src/transaction/staging.rs +228 -434
- package/dist-engine-src/src/transaction/types.rs +41 -98
- package/dist-engine-src/src/transaction/validation.rs +382 -446
- package/dist-engine-src/src/untracked_state/codec.rs +337 -29
- package/dist-engine-src/src/untracked_state/context.rs +7 -7
- package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
- package/dist-engine-src/src/untracked_state/mod.rs +1 -1
- package/dist-engine-src/src/untracked_state/storage.rs +659 -157
- package/dist-engine-src/src/untracked_state/types.rs +21 -21
- package/package.json +71 -68
- package/dist-engine-src/src/backend/kv.rs +0 -358
- package/dist-engine-src/src/backend/testing.rs +0 -658
- package/dist-engine-src/src/commit_store/codec.rs +0 -887
- package/dist-engine-src/src/commit_store/context.rs +0 -944
- package/dist-engine-src/src/commit_store/materialization.rs +0 -84
- package/dist-engine-src/src/commit_store/mod.rs +0 -16
- package/dist-engine-src/src/commit_store/storage.rs +0 -600
- package/dist-engine-src/src/commit_store/types.rs +0 -215
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
- package/dist-engine-src/src/session/create_version.rs +0 -88
- package/dist-engine-src/src/session/merge/apply.rs +0 -23
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
- package/dist-engine-src/src/session/switch_version.rs +0 -110
- package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
- package/dist-engine-src/src/sql2/execute.rs +0 -3533
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
- package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
- package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
- package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
- package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
- package/dist-engine-src/src/sql2/version_scope.rs +0 -394
- package/dist-engine-src/src/storage/types.rs +0 -501
- package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
- package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
- package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
- package/dist-engine-src/src/version/lifecycle.rs +0 -221
- package/dist-engine-src/src/version/mod.rs +0 -13
- package/dist-engine-src/src/version/refs.rs +0 -330
- package/dist-engine-src/src/version/stage_rows.rs +0 -67
- package/dist-engine-src/src/version/types.rs +0 -21
|
@@ -1,70 +1,76 @@
|
|
|
1
1
|
use std::future::Future;
|
|
2
2
|
use std::pin::Pin;
|
|
3
|
-
use std::sync::atomic::{AtomicBool, Ordering};
|
|
4
3
|
use std::sync::Arc;
|
|
5
4
|
|
|
6
5
|
use serde_json::Value as JsonValue;
|
|
7
6
|
|
|
8
7
|
use crate::binary_cas::{BinaryCasContext, BlobDataReader};
|
|
8
|
+
use crate::branch::{
|
|
9
|
+
BranchContext, BranchLifecycle, BranchOperation, BranchRefReader, BranchReferenceRole,
|
|
10
|
+
};
|
|
9
11
|
use crate::catalog::CatalogContext;
|
|
10
12
|
use crate::commit_graph::{CommitGraphContext, CommitGraphReader};
|
|
11
|
-
use crate::
|
|
12
|
-
use crate::entity_identity::EntityIdentity;
|
|
13
|
+
use crate::entity_pk::EntityPk;
|
|
13
14
|
use crate::functions::FunctionProviderHandle;
|
|
14
15
|
use crate::json_store::JsonStoreContext;
|
|
15
16
|
use crate::live_state::{LiveStateContext, LiveStateReader, LiveStateRowRequest};
|
|
16
|
-
use crate::sql2::{
|
|
17
|
+
use crate::sql2::{
|
|
18
|
+
ChangelogQuerySource, HistoryQuerySource, SqlChangelogQuerySource, SqlExecutionContext,
|
|
19
|
+
SqlHistoryQuerySource,
|
|
20
|
+
};
|
|
17
21
|
use crate::storage::{
|
|
18
|
-
|
|
22
|
+
DurableWriteGuard, DurableWriteLock, InMemoryStorageBackend, StorageBackend, StorageReadOptions,
|
|
19
23
|
};
|
|
24
|
+
use crate::storage::{StorageContext, StorageRead, StorageReadScope};
|
|
20
25
|
use crate::tracked_state::TrackedStateContext;
|
|
21
26
|
use crate::transaction::{open_transaction, Transaction};
|
|
22
|
-
use crate::
|
|
23
|
-
VersionContext, VersionLifecycle, VersionOperation, VersionRefReader, VersionReferenceRole,
|
|
24
|
-
};
|
|
25
|
-
use crate::GLOBAL_VERSION_ID;
|
|
27
|
+
use crate::GLOBAL_BRANCH_ID;
|
|
26
28
|
use crate::{LixError, NullableKeyFilter};
|
|
27
29
|
|
|
28
|
-
use super::transaction::
|
|
30
|
+
use super::transaction::{SessionOperationGuard, SessionTransactionManager, SessionWriteLease};
|
|
29
31
|
|
|
30
|
-
pub(crate) const
|
|
32
|
+
pub(crate) const WORKSPACE_BRANCH_KEY: &str = "lix_workspace_branch_id";
|
|
31
33
|
|
|
32
34
|
#[derive(Clone)]
|
|
33
35
|
pub(crate) enum SessionMode {
|
|
34
|
-
Pinned {
|
|
36
|
+
Pinned { branch_id: String },
|
|
35
37
|
Workspace,
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/// Session-context state for engine execution.
|
|
39
41
|
///
|
|
40
|
-
/// A session context pins the active
|
|
42
|
+
/// A session context pins the active branch selector and shared execution
|
|
41
43
|
/// services. Parent-handle `execute(...)` runs as an implicit single-statement
|
|
42
44
|
/// transaction. Explicit transactions hold the session execution lease until
|
|
43
45
|
/// commit or rollback, so all SQL during that window must run through the
|
|
44
46
|
/// transaction handle.
|
|
45
47
|
#[derive(Clone)]
|
|
46
|
-
pub struct SessionContext {
|
|
48
|
+
pub struct SessionContext<B: StorageBackend = InMemoryStorageBackend> {
|
|
47
49
|
pub(super) mode: SessionMode,
|
|
48
|
-
pub(super) storage: StorageContext
|
|
50
|
+
pub(super) storage: StorageContext<B>,
|
|
49
51
|
pub(super) live_state: Arc<LiveStateContext>,
|
|
50
52
|
pub(super) tracked_state: Arc<TrackedStateContext>,
|
|
51
53
|
pub(super) binary_cas: Arc<BinaryCasContext>,
|
|
52
|
-
pub(super)
|
|
53
|
-
pub(super) version_ctx: Arc<VersionContext>,
|
|
54
|
+
pub(super) branch_ctx: Arc<BranchContext>,
|
|
54
55
|
pub(super) catalog_context: Arc<CatalogContext>,
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
pub(super) write_lock: DurableWriteLock,
|
|
57
|
+
transaction_manager: SessionTransactionManager,
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
impl SessionContext
|
|
60
|
+
impl<B> SessionContext<B>
|
|
61
|
+
where
|
|
62
|
+
B: StorageBackend + Clone + Send + Sync + 'static,
|
|
63
|
+
for<'backend> B::Read<'backend>: Clone + Send + Sync + 'static,
|
|
64
|
+
for<'backend> B::Write<'backend>: Send,
|
|
65
|
+
{
|
|
60
66
|
pub(crate) async fn open_workspace(
|
|
61
|
-
storage: StorageContext
|
|
67
|
+
storage: StorageContext<B>,
|
|
62
68
|
live_state: Arc<LiveStateContext>,
|
|
63
69
|
tracked_state: Arc<TrackedStateContext>,
|
|
64
70
|
binary_cas: Arc<BinaryCasContext>,
|
|
65
|
-
|
|
66
|
-
version_ctx: Arc<VersionContext>,
|
|
71
|
+
branch_ctx: Arc<BranchContext>,
|
|
67
72
|
catalog_context: Arc<CatalogContext>,
|
|
73
|
+
write_lock: DurableWriteLock,
|
|
68
74
|
) -> Result<Self, LixError> {
|
|
69
75
|
let session = Self::new(
|
|
70
76
|
SessionMode::Workspace,
|
|
@@ -72,73 +78,71 @@ impl SessionContext {
|
|
|
72
78
|
live_state,
|
|
73
79
|
tracked_state,
|
|
74
80
|
binary_cas,
|
|
75
|
-
|
|
76
|
-
version_ctx,
|
|
81
|
+
branch_ctx,
|
|
77
82
|
catalog_context,
|
|
83
|
+
write_lock,
|
|
78
84
|
);
|
|
79
|
-
session.
|
|
85
|
+
session.active_branch_id().await?;
|
|
80
86
|
Ok(session)
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
pub(crate) async fn open(
|
|
84
|
-
|
|
85
|
-
storage: StorageContext
|
|
90
|
+
active_branch_id: String,
|
|
91
|
+
storage: StorageContext<B>,
|
|
86
92
|
live_state: Arc<LiveStateContext>,
|
|
87
93
|
tracked_state: Arc<TrackedStateContext>,
|
|
88
94
|
binary_cas: Arc<BinaryCasContext>,
|
|
89
|
-
|
|
90
|
-
version_ctx: Arc<VersionContext>,
|
|
95
|
+
branch_ctx: Arc<BranchContext>,
|
|
91
96
|
catalog_context: Arc<CatalogContext>,
|
|
97
|
+
write_lock: DurableWriteLock,
|
|
92
98
|
) -> Result<Self, LixError> {
|
|
93
99
|
Ok(Self::new(
|
|
94
100
|
SessionMode::Pinned {
|
|
95
|
-
|
|
101
|
+
branch_id: active_branch_id,
|
|
96
102
|
},
|
|
97
103
|
storage,
|
|
98
104
|
live_state,
|
|
99
105
|
tracked_state,
|
|
100
106
|
binary_cas,
|
|
101
|
-
|
|
102
|
-
version_ctx,
|
|
107
|
+
branch_ctx,
|
|
103
108
|
catalog_context,
|
|
109
|
+
write_lock,
|
|
104
110
|
))
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
pub(super) fn new(
|
|
108
114
|
mode: SessionMode,
|
|
109
|
-
storage: StorageContext
|
|
115
|
+
storage: StorageContext<B>,
|
|
110
116
|
live_state: Arc<LiveStateContext>,
|
|
111
117
|
tracked_state: Arc<TrackedStateContext>,
|
|
112
118
|
binary_cas: Arc<BinaryCasContext>,
|
|
113
|
-
|
|
114
|
-
version_ctx: Arc<VersionContext>,
|
|
119
|
+
branch_ctx: Arc<BranchContext>,
|
|
115
120
|
catalog_context: Arc<CatalogContext>,
|
|
121
|
+
write_lock: DurableWriteLock,
|
|
116
122
|
) -> Self {
|
|
117
|
-
Self::
|
|
123
|
+
Self::new_with_transaction_manager(
|
|
118
124
|
mode,
|
|
119
125
|
storage,
|
|
120
126
|
live_state,
|
|
121
127
|
tracked_state,
|
|
122
128
|
binary_cas,
|
|
123
|
-
|
|
124
|
-
version_ctx,
|
|
129
|
+
branch_ctx,
|
|
125
130
|
catalog_context,
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
write_lock,
|
|
132
|
+
SessionTransactionManager::new(),
|
|
128
133
|
)
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
pub(super) fn
|
|
136
|
+
pub(super) fn new_with_transaction_manager(
|
|
132
137
|
mode: SessionMode,
|
|
133
|
-
storage: StorageContext
|
|
138
|
+
storage: StorageContext<B>,
|
|
134
139
|
live_state: Arc<LiveStateContext>,
|
|
135
140
|
tracked_state: Arc<TrackedStateContext>,
|
|
136
141
|
binary_cas: Arc<BinaryCasContext>,
|
|
137
|
-
|
|
138
|
-
version_ctx: Arc<VersionContext>,
|
|
142
|
+
branch_ctx: Arc<BranchContext>,
|
|
139
143
|
catalog_context: Arc<CatalogContext>,
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
write_lock: DurableWriteLock,
|
|
145
|
+
transaction_manager: SessionTransactionManager,
|
|
142
146
|
) -> Self {
|
|
143
147
|
Self {
|
|
144
148
|
mode,
|
|
@@ -146,152 +150,194 @@ impl SessionContext {
|
|
|
146
150
|
live_state,
|
|
147
151
|
tracked_state,
|
|
148
152
|
binary_cas,
|
|
149
|
-
|
|
150
|
-
version_ctx,
|
|
153
|
+
branch_ctx,
|
|
151
154
|
catalog_context,
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
write_lock,
|
|
156
|
+
transaction_manager,
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
/// Releases this logical session handle. This is a lifecycle boundary only:
|
|
158
161
|
/// successful writes are committed before their operation returns.
|
|
159
162
|
pub async fn close(&self) -> Result<(), LixError> {
|
|
160
|
-
self.
|
|
161
|
-
Ok(())
|
|
163
|
+
self.transaction_manager.close().await
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
pub fn is_closed(&self) -> bool {
|
|
165
|
-
self.
|
|
167
|
+
self.transaction_manager.is_closed()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#[cfg(test)]
|
|
171
|
+
pub(crate) fn operation_in_progress_count_for_test(&self) -> usize {
|
|
172
|
+
self.transaction_manager.operation_count_for_test()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[cfg(test)]
|
|
176
|
+
pub(crate) fn commit_in_progress_for_test(&self) -> bool {
|
|
177
|
+
self.transaction_manager.commit_in_progress_for_test()
|
|
166
178
|
}
|
|
167
179
|
|
|
168
|
-
|
|
169
|
-
|
|
180
|
+
#[cfg(test)]
|
|
181
|
+
pub(crate) fn active_transaction_for_test(&self) -> bool {
|
|
182
|
+
self.transaction_manager.active_transaction_for_test()
|
|
170
183
|
}
|
|
171
184
|
|
|
172
|
-
pub(
|
|
173
|
-
|
|
185
|
+
pub(super) fn transaction_manager(&self) -> SessionTransactionManager {
|
|
186
|
+
self.transaction_manager.clone()
|
|
174
187
|
}
|
|
175
188
|
|
|
176
189
|
pub(crate) fn ensure_open(&self) -> Result<(), LixError> {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
self.transaction_manager.ensure_open()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
pub(super) fn begin_session_operation(&self) -> Result<SessionOperationGuard, LixError> {
|
|
194
|
+
self.transaction_manager.begin_session_operation()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pub(super) fn begin_session_write_lease(&self) -> Result<SessionWriteLease, LixError> {
|
|
198
|
+
self.transaction_manager.begin_write_lease()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
pub(super) fn begin_explicit_session_write_lease(&self) -> Result<SessionWriteLease, LixError> {
|
|
202
|
+
self.transaction_manager.begin_explicit_write_lease()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
pub(super) async fn begin_session_write_access(&self) -> Result<SessionWriteAccess, LixError> {
|
|
206
|
+
let write_lease = self.begin_session_write_lease()?;
|
|
207
|
+
self.begin_session_write_access_with_lease(write_lease)
|
|
208
|
+
.await
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
pub(super) async fn begin_explicit_session_write_access(
|
|
212
|
+
&self,
|
|
213
|
+
) -> Result<SessionWriteAccess, LixError> {
|
|
214
|
+
let write_lease = self.begin_explicit_session_write_lease()?;
|
|
215
|
+
self.begin_session_write_access_with_lease(write_lease)
|
|
216
|
+
.await
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async fn begin_session_write_access_with_lease(
|
|
220
|
+
&self,
|
|
221
|
+
write_lease: SessionWriteLease,
|
|
222
|
+
) -> Result<SessionWriteAccess, LixError> {
|
|
223
|
+
let write_guard = self.write_lock.lock_owned().await;
|
|
224
|
+
let write_access = SessionWriteAccess {
|
|
225
|
+
_write_lease: write_lease,
|
|
226
|
+
_write_guard: write_guard,
|
|
227
|
+
};
|
|
228
|
+
self.ensure_open()?;
|
|
229
|
+
Ok(write_access)
|
|
181
230
|
}
|
|
182
231
|
|
|
183
|
-
/// Resolves the
|
|
232
|
+
/// Resolves the branch this session should operate on right now.
|
|
184
233
|
///
|
|
185
|
-
/// This is a read-path helper. Write flows must resolve the active
|
|
234
|
+
/// This is a read-path helper. Write flows must resolve the active branch
|
|
186
235
|
/// through the transaction capability so the read is scoped to the
|
|
187
236
|
/// same backend transaction as the writes it influences.
|
|
188
237
|
///
|
|
189
|
-
/// Pinned sessions are pure in-memory views over one
|
|
238
|
+
/// Pinned sessions are pure in-memory views over one branch. Workspace
|
|
190
239
|
/// sessions read the shared workspace selector from untracked global
|
|
191
240
|
/// `lix_key_value` state so multiple open app sessions can observe the same
|
|
192
|
-
/// active workspace
|
|
193
|
-
pub async fn
|
|
194
|
-
let
|
|
195
|
-
let
|
|
196
|
-
|
|
197
|
-
.await;
|
|
241
|
+
/// active workspace branch.
|
|
242
|
+
pub async fn active_branch_id(&self) -> Result<String, LixError> {
|
|
243
|
+
let _operation_guard = self.begin_session_operation()?;
|
|
244
|
+
let transaction = self.storage.begin_read(StorageReadOptions::default())?;
|
|
245
|
+
let result = self.active_branch_id_from_reader(&transaction).await;
|
|
198
246
|
match result {
|
|
199
|
-
Ok(
|
|
200
|
-
|
|
201
|
-
Ok(version_id)
|
|
202
|
-
}
|
|
203
|
-
Err(error) => {
|
|
204
|
-
let _ = transaction.rollback().await;
|
|
205
|
-
Err(error)
|
|
206
|
-
}
|
|
247
|
+
Ok(branch_id) => Ok(branch_id),
|
|
248
|
+
Err(error) => Err(error),
|
|
207
249
|
}
|
|
208
250
|
}
|
|
209
251
|
|
|
210
|
-
pub(super) async fn
|
|
252
|
+
pub(super) async fn active_branch_id_from_reader<S>(
|
|
211
253
|
&self,
|
|
212
|
-
reader: &
|
|
254
|
+
reader: &S,
|
|
213
255
|
) -> Result<String, LixError>
|
|
214
256
|
where
|
|
215
|
-
S:
|
|
257
|
+
S: StorageRead + Send + Sync + ?Sized,
|
|
216
258
|
{
|
|
217
259
|
self.ensure_open()?;
|
|
218
260
|
match &self.mode {
|
|
219
|
-
SessionMode::Pinned {
|
|
220
|
-
SessionMode::Workspace => self.
|
|
261
|
+
SessionMode::Pinned { branch_id } => Ok(branch_id.clone()),
|
|
262
|
+
SessionMode::Workspace => self.load_workspace_branch_id(reader).await,
|
|
221
263
|
}
|
|
222
264
|
}
|
|
223
265
|
|
|
224
|
-
async fn
|
|
266
|
+
async fn load_workspace_branch_id<S>(&self, reader: &S) -> Result<String, LixError>
|
|
225
267
|
where
|
|
226
|
-
S:
|
|
268
|
+
S: StorageRead + Send + Sync + ?Sized,
|
|
227
269
|
{
|
|
228
270
|
let row = self
|
|
229
271
|
.live_state
|
|
230
|
-
.reader(
|
|
272
|
+
.reader(reader)
|
|
231
273
|
.load_row(&LiveStateRowRequest {
|
|
232
274
|
schema_key: "lix_key_value".to_string(),
|
|
233
|
-
|
|
234
|
-
|
|
275
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
276
|
+
entity_pk: EntityPk::single(WORKSPACE_BRANCH_KEY),
|
|
235
277
|
file_id: NullableKeyFilter::Null,
|
|
236
278
|
})
|
|
237
279
|
.await?
|
|
238
280
|
.ok_or_else(|| {
|
|
239
281
|
LixError::new(
|
|
240
282
|
"LIX_ERROR_UNKNOWN",
|
|
241
|
-
"workspace
|
|
283
|
+
"workspace branch selector is missing lix_key_value:lix_workspace_branch_id",
|
|
242
284
|
)
|
|
243
285
|
})?;
|
|
244
286
|
let snapshot_content = row.snapshot_content.as_deref().ok_or_else(|| {
|
|
245
287
|
LixError::new(
|
|
246
288
|
"LIX_ERROR_UNKNOWN",
|
|
247
|
-
"workspace
|
|
289
|
+
"workspace branch selector is missing snapshot_content",
|
|
248
290
|
)
|
|
249
291
|
})?;
|
|
250
292
|
let snapshot = serde_json::from_str::<JsonValue>(snapshot_content).map_err(|error| {
|
|
251
293
|
LixError::new(
|
|
252
294
|
"LIX_ERROR_UNKNOWN",
|
|
253
|
-
format!("workspace
|
|
295
|
+
format!("workspace branch selector snapshot is invalid JSON: {error}"),
|
|
254
296
|
)
|
|
255
297
|
})?;
|
|
256
|
-
let
|
|
298
|
+
let branch_id = snapshot
|
|
257
299
|
.get("value")
|
|
258
300
|
.and_then(JsonValue::as_str)
|
|
259
301
|
.filter(|value| !value.is_empty())
|
|
260
302
|
.ok_or_else(|| {
|
|
261
303
|
LixError::new(
|
|
262
304
|
"LIX_ERROR_UNKNOWN",
|
|
263
|
-
"workspace
|
|
305
|
+
"workspace branch selector value must be a non-empty string",
|
|
264
306
|
)
|
|
265
307
|
})?
|
|
266
308
|
.to_string();
|
|
267
309
|
|
|
268
|
-
let
|
|
269
|
-
|
|
310
|
+
let branch_ref = self.branch_ctx.ref_reader(reader);
|
|
311
|
+
BranchLifecycle::new(&branch_ref)
|
|
270
312
|
.require_existing_ref(
|
|
271
|
-
&
|
|
272
|
-
|
|
273
|
-
|
|
313
|
+
&branch_id,
|
|
314
|
+
BranchOperation::LoadWorkspaceSelector,
|
|
315
|
+
BranchReferenceRole::WorkspaceSelector,
|
|
274
316
|
)
|
|
275
317
|
.await?;
|
|
276
318
|
|
|
277
|
-
Ok(
|
|
319
|
+
Ok(branch_id)
|
|
278
320
|
}
|
|
279
321
|
|
|
280
322
|
pub(crate) async fn with_write_transaction<T, F>(&self, f: F) -> Result<T, LixError>
|
|
281
323
|
where
|
|
282
324
|
F: for<'tx> FnOnce(
|
|
283
|
-
&'tx mut Transaction
|
|
325
|
+
&'tx mut Transaction<B>,
|
|
284
326
|
) -> Pin<Box<dyn Future<Output = Result<T, LixError>> + 'tx>>,
|
|
285
327
|
{
|
|
286
328
|
self.ensure_open()?;
|
|
287
|
-
let
|
|
288
|
-
self.with_write_transaction_reserved(f).await
|
|
329
|
+
let write_access = self.begin_session_write_access().await?;
|
|
330
|
+
self.with_write_transaction_reserved(write_access, f).await
|
|
289
331
|
}
|
|
290
332
|
|
|
291
|
-
pub(
|
|
333
|
+
pub(super) async fn with_write_transaction_reserved<T, F>(
|
|
334
|
+
&self,
|
|
335
|
+
_write_access: SessionWriteAccess,
|
|
336
|
+
f: F,
|
|
337
|
+
) -> Result<T, LixError>
|
|
292
338
|
where
|
|
293
339
|
F: for<'tx> FnOnce(
|
|
294
|
-
&'tx mut Transaction
|
|
340
|
+
&'tx mut Transaction<B>,
|
|
295
341
|
) -> Pin<Box<dyn Future<Output = Result<T, LixError>> + 'tx>>,
|
|
296
342
|
{
|
|
297
343
|
let opened = open_transaction(
|
|
@@ -300,85 +346,85 @@ impl SessionContext {
|
|
|
300
346
|
Arc::clone(&self.live_state),
|
|
301
347
|
Arc::clone(&self.tracked_state),
|
|
302
348
|
Arc::clone(&self.binary_cas),
|
|
303
|
-
Arc::clone(&self.
|
|
304
|
-
Arc::clone(&self.version_ctx),
|
|
349
|
+
Arc::clone(&self.branch_ctx),
|
|
305
350
|
Arc::clone(&self.catalog_context),
|
|
306
351
|
)
|
|
307
352
|
.await?;
|
|
353
|
+
self.ensure_open()?;
|
|
308
354
|
let mut transaction = opened.transaction;
|
|
355
|
+
transaction.attach_commit_boundary(self.transaction_commit_boundary());
|
|
309
356
|
let runtime_functions = opened.runtime_functions;
|
|
310
357
|
|
|
311
358
|
match f(&mut transaction).await {
|
|
312
359
|
Ok(value) => {
|
|
360
|
+
self.ensure_open()?;
|
|
313
361
|
transaction.commit(&runtime_functions).await?;
|
|
314
362
|
Ok(value)
|
|
315
363
|
}
|
|
316
|
-
Err(error) =>
|
|
317
|
-
let _ = transaction.rollback().await;
|
|
318
|
-
Err(error)
|
|
319
|
-
}
|
|
364
|
+
Err(error) => Err(error),
|
|
320
365
|
}
|
|
321
366
|
}
|
|
322
367
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
|
327
|
-
.is_err()
|
|
328
|
-
{
|
|
329
|
-
return Err(transaction_state_error(
|
|
330
|
-
"Lix handle has an active transaction; use the transaction handle for reads and writes until it is committed or rolled back",
|
|
331
|
-
));
|
|
332
|
-
}
|
|
333
|
-
Ok(SessionTransactionGuard { active_transaction })
|
|
368
|
+
#[cfg(test)]
|
|
369
|
+
pub(super) fn begin_commit(&self) -> crate::transaction::CommitBoundaryGuard {
|
|
370
|
+
self.transaction_manager.begin_commit()
|
|
334
371
|
}
|
|
335
|
-
}
|
|
336
372
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
373
|
+
pub(super) fn transaction_commit_boundary(
|
|
374
|
+
&self,
|
|
375
|
+
) -> crate::transaction::TransactionCommitBoundary {
|
|
376
|
+
self.transaction_manager.transaction_commit_boundary()
|
|
377
|
+
}
|
|
340
378
|
}
|
|
341
379
|
|
|
342
|
-
pub(super) struct
|
|
343
|
-
|
|
380
|
+
pub(super) struct SessionWriteAccess {
|
|
381
|
+
_write_guard: DurableWriteGuard,
|
|
382
|
+
_write_lease: SessionWriteLease,
|
|
344
383
|
}
|
|
345
384
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
385
|
+
pub(super) fn closed_error() -> LixError {
|
|
386
|
+
LixError::new(LixError::CODE_CLOSED, "Lix handle is closed")
|
|
387
|
+
.with_hint("Open a new Lix handle before calling this method.")
|
|
350
388
|
}
|
|
351
389
|
|
|
352
390
|
/// Read-only SQL execution context derived from a session.
|
|
353
391
|
///
|
|
354
392
|
/// Write statements re-plan against `Transaction`; this context intentionally
|
|
355
393
|
/// has no write stager.
|
|
356
|
-
pub(super) struct SessionSqlExecutionContext<'a> {
|
|
357
|
-
pub(super)
|
|
358
|
-
pub(super) read_store:
|
|
359
|
-
ScopedStorageReader<Box<dyn StorageReadTransaction + Send + Sync + 'static>>,
|
|
394
|
+
pub(super) struct SessionSqlExecutionContext<'a, R> {
|
|
395
|
+
pub(super) active_branch_id: &'a str,
|
|
396
|
+
pub(super) read_store: StorageReadScope<R>,
|
|
360
397
|
pub(super) live_state: Arc<LiveStateContext>,
|
|
361
398
|
pub(super) binary_cas: Arc<BinaryCasContext>,
|
|
362
|
-
pub(super)
|
|
363
|
-
pub(super) version_ctx: Arc<VersionContext>,
|
|
399
|
+
pub(super) branch_ctx: Arc<BranchContext>,
|
|
364
400
|
pub(super) visible_schemas: Vec<JsonValue>,
|
|
365
401
|
pub(super) functions: FunctionProviderHandle,
|
|
366
402
|
}
|
|
367
403
|
|
|
368
|
-
impl SqlExecutionContext for SessionSqlExecutionContext<'_>
|
|
369
|
-
|
|
370
|
-
|
|
404
|
+
impl<R> SqlExecutionContext for SessionSqlExecutionContext<'_, R>
|
|
405
|
+
where
|
|
406
|
+
R: crate::storage::StorageBackendRead + Clone + Send + Sync + 'static,
|
|
407
|
+
{
|
|
408
|
+
type ReadStore = StorageReadScope<R>;
|
|
409
|
+
|
|
410
|
+
fn active_branch_id(&self) -> &str {
|
|
411
|
+
self.active_branch_id
|
|
371
412
|
}
|
|
372
413
|
|
|
373
414
|
fn live_state(&self) -> Arc<dyn LiveStateReader> {
|
|
374
415
|
Arc::new(self.live_state.reader(self.read_store.clone())) as Arc<dyn LiveStateReader>
|
|
375
416
|
}
|
|
376
417
|
|
|
377
|
-
fn
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
418
|
+
fn history_query_source(&self) -> SqlHistoryQuerySource<Self::ReadStore> {
|
|
419
|
+
HistoryQuerySource {
|
|
420
|
+
json_reader: JsonStoreContext::new().reader(self.read_store.store()),
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
fn changelog_query_source(&self) -> SqlChangelogQuerySource<Self::ReadStore> {
|
|
425
|
+
ChangelogQuerySource {
|
|
426
|
+
store: self.read_store.clone(),
|
|
427
|
+
json_reader: JsonStoreContext::new().reader(self.read_store.store()),
|
|
382
428
|
}
|
|
383
429
|
}
|
|
384
430
|
|
|
@@ -386,8 +432,8 @@ impl SqlExecutionContext for SessionSqlExecutionContext<'_> {
|
|
|
386
432
|
Box::new(CommitGraphContext::new().reader(self.read_store.clone()))
|
|
387
433
|
}
|
|
388
434
|
|
|
389
|
-
fn
|
|
390
|
-
Arc::new(self.
|
|
435
|
+
fn branch_ref(&self) -> Arc<dyn BranchRefReader> {
|
|
436
|
+
Arc::new(self.branch_ctx.ref_reader(self.read_store.clone()))
|
|
391
437
|
}
|
|
392
438
|
|
|
393
439
|
fn functions(&self) -> FunctionProviderHandle {
|
|
@@ -402,3 +448,612 @@ impl SqlExecutionContext for SessionSqlExecutionContext<'_> {
|
|
|
402
448
|
Ok(self.visible_schemas.clone())
|
|
403
449
|
}
|
|
404
450
|
}
|
|
451
|
+
|
|
452
|
+
#[cfg(test)]
|
|
453
|
+
mod tests {
|
|
454
|
+
use std::future::Future;
|
|
455
|
+
use std::pin::Pin;
|
|
456
|
+
use std::sync::Condvar;
|
|
457
|
+
use std::sync::Mutex;
|
|
458
|
+
use std::task::{Context, Poll};
|
|
459
|
+
use std::thread;
|
|
460
|
+
use std::time::{Duration, Instant};
|
|
461
|
+
|
|
462
|
+
use crate::backend::{
|
|
463
|
+
Backend, BackendCapabilities, BackendError, DurableWriteLock, InMemoryBackend,
|
|
464
|
+
InMemoryRead, InMemoryWrite, ReadOptions, WriteOptions,
|
|
465
|
+
};
|
|
466
|
+
use crate::Engine;
|
|
467
|
+
use futures_util::task::noop_waker_ref;
|
|
468
|
+
|
|
469
|
+
const TEST_WAIT_TIMEOUT: Duration = Duration::from_secs(2);
|
|
470
|
+
|
|
471
|
+
fn wait_until(description: &str, mut condition: impl FnMut() -> bool) {
|
|
472
|
+
let deadline = Instant::now() + TEST_WAIT_TIMEOUT;
|
|
473
|
+
while !condition() {
|
|
474
|
+
assert!(
|
|
475
|
+
Instant::now() < deadline,
|
|
476
|
+
"timed out waiting for {description}"
|
|
477
|
+
);
|
|
478
|
+
thread::yield_now();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
fn assert_close_pending<F>(mut future: Pin<&mut F>)
|
|
483
|
+
where
|
|
484
|
+
F: Future<Output = Result<(), crate::LixError>>,
|
|
485
|
+
{
|
|
486
|
+
let mut cx = Context::from_waker(noop_waker_ref());
|
|
487
|
+
assert!(
|
|
488
|
+
matches!(future.as_mut().poll(&mut cx), Poll::Pending),
|
|
489
|
+
"close should remain pending while guarded work is in progress"
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async fn assert_close_finishes<F>(future: Pin<&mut F>, description: &str)
|
|
494
|
+
where
|
|
495
|
+
F: Future<Output = Result<(), crate::LixError>>,
|
|
496
|
+
{
|
|
497
|
+
tokio::time::timeout(TEST_WAIT_TIMEOUT, future)
|
|
498
|
+
.await
|
|
499
|
+
.unwrap_or_else(|_| panic!("timed out waiting for {description}"))
|
|
500
|
+
.unwrap_or_else(|error| panic!("{description} failed: {error:?}"));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
fn join_thread<T>(handle: thread::JoinHandle<T>, description: &str) -> T {
|
|
504
|
+
wait_until(description, || handle.is_finished());
|
|
505
|
+
match handle.join() {
|
|
506
|
+
Ok(result) => result,
|
|
507
|
+
Err(_) => panic!("{description} panicked"),
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async fn open_session() -> std::sync::Arc<super::SessionContext<InMemoryBackend>> {
|
|
512
|
+
let backend = InMemoryBackend::default();
|
|
513
|
+
let _receipt = Engine::initialize(backend.clone())
|
|
514
|
+
.await
|
|
515
|
+
.expect("backend should initialize");
|
|
516
|
+
let engine = Engine::new(backend)
|
|
517
|
+
.await
|
|
518
|
+
.expect("initialized backend should create engine");
|
|
519
|
+
std::sync::Arc::new(
|
|
520
|
+
engine
|
|
521
|
+
.open_workspace_session()
|
|
522
|
+
.await
|
|
523
|
+
.expect("workspace session should open"),
|
|
524
|
+
)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async fn open_blocking_read_session() -> (
|
|
528
|
+
std::sync::Arc<super::SessionContext<BlockingBeginReadBackend>>,
|
|
529
|
+
BlockingGate,
|
|
530
|
+
) {
|
|
531
|
+
let backend = BlockingBeginReadBackend::new();
|
|
532
|
+
let gate = backend.gate();
|
|
533
|
+
let _receipt = Engine::initialize(backend.clone())
|
|
534
|
+
.await
|
|
535
|
+
.expect("backend should initialize");
|
|
536
|
+
let engine = Engine::new(backend)
|
|
537
|
+
.await
|
|
538
|
+
.expect("initialized backend should create engine");
|
|
539
|
+
(
|
|
540
|
+
std::sync::Arc::new(
|
|
541
|
+
engine
|
|
542
|
+
.open_workspace_session()
|
|
543
|
+
.await
|
|
544
|
+
.expect("workspace session should open"),
|
|
545
|
+
),
|
|
546
|
+
gate,
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async fn open_blocking_write_session() -> (
|
|
551
|
+
std::sync::Arc<super::SessionContext<BlockingBeginWriteBackend>>,
|
|
552
|
+
BlockingGate,
|
|
553
|
+
) {
|
|
554
|
+
let backend = BlockingBeginWriteBackend::new();
|
|
555
|
+
let gate = backend.gate();
|
|
556
|
+
let _receipt = Engine::initialize(backend.clone())
|
|
557
|
+
.await
|
|
558
|
+
.expect("backend should initialize");
|
|
559
|
+
let engine = Engine::new(backend)
|
|
560
|
+
.await
|
|
561
|
+
.expect("initialized backend should create engine");
|
|
562
|
+
(
|
|
563
|
+
std::sync::Arc::new(
|
|
564
|
+
engine
|
|
565
|
+
.open_workspace_session()
|
|
566
|
+
.await
|
|
567
|
+
.expect("workspace session should open"),
|
|
568
|
+
),
|
|
569
|
+
gate,
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
#[tokio::test]
|
|
574
|
+
async fn close_waits_for_session_operation_guard_to_drop() {
|
|
575
|
+
let session = open_session().await;
|
|
576
|
+
let guard = session
|
|
577
|
+
.begin_session_operation()
|
|
578
|
+
.expect("session operation should begin");
|
|
579
|
+
let mut close = Box::pin(session.close());
|
|
580
|
+
assert_close_pending(close.as_mut());
|
|
581
|
+
|
|
582
|
+
drop(guard);
|
|
583
|
+
assert_close_finishes(close.as_mut(), "close after operation guard drops").await;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
#[tokio::test]
|
|
587
|
+
async fn close_waits_for_commit_guard_to_drop() {
|
|
588
|
+
let session = open_session().await;
|
|
589
|
+
let guard = session.begin_commit();
|
|
590
|
+
let mut close = Box::pin(session.close());
|
|
591
|
+
assert_close_pending(close.as_mut());
|
|
592
|
+
|
|
593
|
+
drop(guard);
|
|
594
|
+
assert_close_finishes(close.as_mut(), "close after commit guard drops").await;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
#[tokio::test]
|
|
598
|
+
async fn session_read_execute_holds_operation_guard() {
|
|
599
|
+
let session = open_session().await;
|
|
600
|
+
let result = session
|
|
601
|
+
.execute("SELECT 1", &[])
|
|
602
|
+
.await
|
|
603
|
+
.expect("read should succeed");
|
|
604
|
+
assert_eq!(result.len(), 1);
|
|
605
|
+
assert_eq!(session.operation_in_progress_count_for_test(), 0);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
#[tokio::test]
|
|
609
|
+
async fn active_transaction_read_execute_holds_operation_guard() {
|
|
610
|
+
let session = open_session().await;
|
|
611
|
+
let mut transaction = session
|
|
612
|
+
.begin_transaction()
|
|
613
|
+
.await
|
|
614
|
+
.expect("transaction should begin");
|
|
615
|
+
assert!(session.active_transaction_for_test());
|
|
616
|
+
let result = transaction
|
|
617
|
+
.execute("SELECT 1", &[])
|
|
618
|
+
.await
|
|
619
|
+
.expect("transaction read should succeed");
|
|
620
|
+
assert_eq!(result.len(), 1);
|
|
621
|
+
assert_eq!(session.operation_in_progress_count_for_test(), 1);
|
|
622
|
+
assert!(session.active_transaction_for_test());
|
|
623
|
+
transaction
|
|
624
|
+
.rollback()
|
|
625
|
+
.await
|
|
626
|
+
.expect("transaction rollback should succeed");
|
|
627
|
+
assert_eq!(session.operation_in_progress_count_for_test(), 0);
|
|
628
|
+
assert!(!session.active_transaction_for_test());
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
#[tokio::test]
|
|
632
|
+
async fn close_rejects_idle_explicit_transaction_without_waiting() {
|
|
633
|
+
let session = open_session().await;
|
|
634
|
+
let transaction = session
|
|
635
|
+
.begin_transaction()
|
|
636
|
+
.await
|
|
637
|
+
.expect("transaction should begin");
|
|
638
|
+
|
|
639
|
+
let error = session
|
|
640
|
+
.close()
|
|
641
|
+
.await
|
|
642
|
+
.expect_err("close should reject an idle explicit transaction");
|
|
643
|
+
assert_eq!(error.code, "LIX_INVALID_TRANSACTION_STATE");
|
|
644
|
+
|
|
645
|
+
transaction
|
|
646
|
+
.rollback()
|
|
647
|
+
.await
|
|
648
|
+
.expect("rollback should remain available after rejected close");
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
#[tokio::test]
|
|
652
|
+
async fn transaction_open_waits_for_write_lock() {
|
|
653
|
+
let session = open_session().await;
|
|
654
|
+
let write_guard = session.write_lock.lock_owned().await;
|
|
655
|
+
|
|
656
|
+
let opener_session = std::sync::Arc::clone(&session);
|
|
657
|
+
let opener = thread::spawn(move || {
|
|
658
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
659
|
+
.build()
|
|
660
|
+
.expect("test runtime should build");
|
|
661
|
+
runtime.block_on(async move { opener_session.begin_transaction().await })
|
|
662
|
+
});
|
|
663
|
+
wait_until("explicit transaction open to reserve the session", || {
|
|
664
|
+
session.operation_in_progress_count_for_test() > 0
|
|
665
|
+
&& session.active_transaction_for_test()
|
|
666
|
+
&& !opener.is_finished()
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
assert!(
|
|
670
|
+
!opener.is_finished(),
|
|
671
|
+
"transaction open should wait for the write lock"
|
|
672
|
+
);
|
|
673
|
+
assert!(session.active_transaction_for_test());
|
|
674
|
+
|
|
675
|
+
drop(write_guard);
|
|
676
|
+
let transaction = join_thread(opener, "queued transaction opener")
|
|
677
|
+
.expect("transaction should begin after write lock is released");
|
|
678
|
+
transaction
|
|
679
|
+
.rollback()
|
|
680
|
+
.await
|
|
681
|
+
.expect("transaction rollback should succeed");
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
#[tokio::test]
|
|
685
|
+
async fn close_waits_for_session_write_queued_on_write_lock() {
|
|
686
|
+
let session = open_session().await;
|
|
687
|
+
let write_guard = session.write_lock.lock_owned().await;
|
|
688
|
+
|
|
689
|
+
let writer_session = std::sync::Arc::clone(&session);
|
|
690
|
+
let writer = thread::spawn(move || {
|
|
691
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
692
|
+
.build()
|
|
693
|
+
.expect("test runtime should build");
|
|
694
|
+
runtime.block_on(async move {
|
|
695
|
+
writer_session
|
|
696
|
+
.execute(
|
|
697
|
+
"INSERT INTO lix_key_value (key, value) VALUES ('queued-write-close', 'value')",
|
|
698
|
+
&[],
|
|
699
|
+
)
|
|
700
|
+
.await
|
|
701
|
+
})
|
|
702
|
+
});
|
|
703
|
+
wait_until("queued session write to reserve the session", || {
|
|
704
|
+
session.operation_in_progress_count_for_test() > 0
|
|
705
|
+
&& session.active_transaction_for_test()
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
let mut close = Box::pin(session.close());
|
|
709
|
+
assert_close_pending(close.as_mut());
|
|
710
|
+
|
|
711
|
+
drop(write_guard);
|
|
712
|
+
let write_error =
|
|
713
|
+
join_thread(writer, "queued writer").expect_err("queued write should observe close");
|
|
714
|
+
assert_eq!(write_error.code, crate::LixError::CODE_CLOSED);
|
|
715
|
+
assert_close_finishes(close.as_mut(), "close after queued write exits").await;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
#[tokio::test]
|
|
719
|
+
async fn session_read_does_not_wait_for_write_lock() {
|
|
720
|
+
let session = open_session().await;
|
|
721
|
+
let write_guard = session.write_lock.lock_owned().await;
|
|
722
|
+
|
|
723
|
+
let result = tokio::time::timeout(TEST_WAIT_TIMEOUT, session.execute("SELECT 1", &[]))
|
|
724
|
+
.await
|
|
725
|
+
.expect("read should not wait for the write lock")
|
|
726
|
+
.expect("read should succeed");
|
|
727
|
+
|
|
728
|
+
assert_eq!(result.len(), 1);
|
|
729
|
+
drop(write_guard);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
#[tokio::test]
|
|
733
|
+
async fn explicit_transaction_commit_sets_commit_guard() {
|
|
734
|
+
let session = open_session().await;
|
|
735
|
+
let mut transaction = session
|
|
736
|
+
.begin_transaction()
|
|
737
|
+
.await
|
|
738
|
+
.expect("transaction should begin");
|
|
739
|
+
transaction
|
|
740
|
+
.execute(
|
|
741
|
+
"INSERT INTO lix_key_value (key, value) VALUES ('commit-guard-test', 'value')",
|
|
742
|
+
&[],
|
|
743
|
+
)
|
|
744
|
+
.await
|
|
745
|
+
.expect("transaction write should stage");
|
|
746
|
+
transaction
|
|
747
|
+
.commit()
|
|
748
|
+
.await
|
|
749
|
+
.expect("transaction commit should succeed");
|
|
750
|
+
assert!(!session.commit_in_progress_for_test());
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
#[tokio::test]
|
|
754
|
+
async fn close_waits_for_explicit_transaction_open_queued_on_write_lock() {
|
|
755
|
+
let session = open_session().await;
|
|
756
|
+
let write_guard = session.write_lock.lock_owned().await;
|
|
757
|
+
|
|
758
|
+
let opener_session = std::sync::Arc::clone(&session);
|
|
759
|
+
let opener = thread::spawn(move || {
|
|
760
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
761
|
+
.build()
|
|
762
|
+
.expect("test runtime should build");
|
|
763
|
+
runtime.block_on(async move { opener_session.begin_transaction().await })
|
|
764
|
+
});
|
|
765
|
+
wait_until("explicit transaction open to queue on write lock", || {
|
|
766
|
+
session.operation_in_progress_count_for_test() > 0
|
|
767
|
+
&& session.active_transaction_for_test()
|
|
768
|
+
&& !opener.is_finished()
|
|
769
|
+
});
|
|
770
|
+
assert!(
|
|
771
|
+
!opener.is_finished(),
|
|
772
|
+
"transaction open should still be queued on write lock"
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
let mut close = Box::pin(session.close());
|
|
776
|
+
assert_close_pending(close.as_mut());
|
|
777
|
+
|
|
778
|
+
drop(write_guard);
|
|
779
|
+
let open_error = match join_thread(opener, "queued explicit transaction opener") {
|
|
780
|
+
Ok(_) => panic!("queued explicit transaction open should observe close"),
|
|
781
|
+
Err(error) => error,
|
|
782
|
+
};
|
|
783
|
+
assert_eq!(open_error.code, crate::LixError::CODE_CLOSED);
|
|
784
|
+
assert_close_finishes(close.as_mut(), "close after queued explicit open exits").await;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
#[tokio::test]
|
|
788
|
+
async fn close_waits_for_session_read_blocked_in_backend_read() {
|
|
789
|
+
let (session, gate) = open_blocking_read_session().await;
|
|
790
|
+
|
|
791
|
+
gate.block_next();
|
|
792
|
+
let reader_session = std::sync::Arc::clone(&session);
|
|
793
|
+
let reader = std::thread::spawn(move || {
|
|
794
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
795
|
+
.build()
|
|
796
|
+
.expect("test runtime should build");
|
|
797
|
+
runtime.block_on(async move { reader_session.execute("SELECT 1", &[]).await })
|
|
798
|
+
});
|
|
799
|
+
gate.wait_until_blocked();
|
|
800
|
+
|
|
801
|
+
let mut close = Box::pin(session.close());
|
|
802
|
+
assert_close_pending(close.as_mut());
|
|
803
|
+
|
|
804
|
+
gate.release();
|
|
805
|
+
let error = join_thread(reader, "blocked reader")
|
|
806
|
+
.expect_err("read should observe close after backend read resumes");
|
|
807
|
+
assert_eq!(error.code, crate::LixError::CODE_CLOSED);
|
|
808
|
+
assert_close_finishes(close.as_mut(), "close after blocked read exits").await;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
#[tokio::test]
|
|
812
|
+
async fn close_rejects_active_transaction_read_blocked_in_backend_read() {
|
|
813
|
+
let (session, gate) = open_blocking_read_session().await;
|
|
814
|
+
let mut transaction = session
|
|
815
|
+
.begin_transaction()
|
|
816
|
+
.await
|
|
817
|
+
.expect("transaction should begin");
|
|
818
|
+
|
|
819
|
+
gate.block_next();
|
|
820
|
+
let reader = std::thread::spawn(move || {
|
|
821
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
822
|
+
.build()
|
|
823
|
+
.expect("test runtime should build");
|
|
824
|
+
runtime.block_on(async move { transaction.execute("SELECT 1", &[]).await })
|
|
825
|
+
});
|
|
826
|
+
gate.wait_until_blocked();
|
|
827
|
+
|
|
828
|
+
let close_error = session
|
|
829
|
+
.close()
|
|
830
|
+
.await
|
|
831
|
+
.expect_err("close should reject an active explicit transaction read");
|
|
832
|
+
assert_eq!(close_error.code, "LIX_INVALID_TRANSACTION_STATE");
|
|
833
|
+
|
|
834
|
+
gate.release();
|
|
835
|
+
let result = join_thread(reader, "blocked transaction reader")
|
|
836
|
+
.expect("in-flight transaction read should finish after rejected close");
|
|
837
|
+
assert_eq!(result.len(), 1);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
#[tokio::test]
|
|
841
|
+
async fn close_waits_for_explicit_transaction_blocked_in_backend_commit() {
|
|
842
|
+
let (session, gate) = open_blocking_write_session().await;
|
|
843
|
+
let mut transaction = session
|
|
844
|
+
.begin_transaction()
|
|
845
|
+
.await
|
|
846
|
+
.expect("transaction should begin");
|
|
847
|
+
transaction
|
|
848
|
+
.execute(
|
|
849
|
+
"INSERT INTO lix_key_value (key, value) VALUES ('blocked-commit', 'value')",
|
|
850
|
+
&[],
|
|
851
|
+
)
|
|
852
|
+
.await
|
|
853
|
+
.expect("transaction write should stage");
|
|
854
|
+
|
|
855
|
+
gate.block_next();
|
|
856
|
+
let committer = std::thread::spawn(move || {
|
|
857
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
858
|
+
.build()
|
|
859
|
+
.expect("test runtime should build");
|
|
860
|
+
runtime.block_on(async move { transaction.commit().await })
|
|
861
|
+
});
|
|
862
|
+
gate.wait_until_blocked();
|
|
863
|
+
assert!(
|
|
864
|
+
session.commit_in_progress_for_test(),
|
|
865
|
+
"blocked explicit transaction commit should set the commit guard"
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
let mut close = Box::pin(session.close());
|
|
869
|
+
assert_close_pending(close.as_mut());
|
|
870
|
+
|
|
871
|
+
gate.release();
|
|
872
|
+
join_thread(committer, "blocked committer")
|
|
873
|
+
.expect("commit already at durable boundary should finish");
|
|
874
|
+
assert_close_finishes(close.as_mut(), "close after commit exits").await;
|
|
875
|
+
assert!(
|
|
876
|
+
!session.commit_in_progress_for_test(),
|
|
877
|
+
"commit guard should clear after the blocked commit exits"
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
#[derive(Clone)]
|
|
882
|
+
struct BlockingBeginReadBackend {
|
|
883
|
+
inner: InMemoryBackend,
|
|
884
|
+
gate: BlockingGate,
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
impl BlockingBeginReadBackend {
|
|
888
|
+
fn new() -> Self {
|
|
889
|
+
Self {
|
|
890
|
+
inner: InMemoryBackend::default(),
|
|
891
|
+
gate: BlockingGate::new(),
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
fn gate(&self) -> BlockingGate {
|
|
896
|
+
self.gate.clone()
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
impl Backend for BlockingBeginReadBackend {
|
|
901
|
+
type Read<'a>
|
|
902
|
+
= InMemoryRead
|
|
903
|
+
where
|
|
904
|
+
Self: 'a;
|
|
905
|
+
|
|
906
|
+
type Write<'a>
|
|
907
|
+
= InMemoryWrite
|
|
908
|
+
where
|
|
909
|
+
Self: 'a;
|
|
910
|
+
|
|
911
|
+
fn capabilities(&self) -> BackendCapabilities {
|
|
912
|
+
self.inner.capabilities()
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
fn begin_read(&self, opts: ReadOptions) -> Result<Self::Read<'_>, BackendError> {
|
|
916
|
+
self.gate.maybe_block();
|
|
917
|
+
self.inner.begin_read(opts)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
fn begin_write(&self, opts: WriteOptions) -> Result<Self::Write<'_>, BackendError> {
|
|
921
|
+
self.inner.begin_write(opts)
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
fn durable_write_lock(&self) -> DurableWriteLock {
|
|
925
|
+
self.inner.durable_write_lock()
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
#[derive(Clone)]
|
|
930
|
+
struct BlockingBeginWriteBackend {
|
|
931
|
+
inner: InMemoryBackend,
|
|
932
|
+
gate: BlockingGate,
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
impl BlockingBeginWriteBackend {
|
|
936
|
+
fn new() -> Self {
|
|
937
|
+
Self {
|
|
938
|
+
inner: InMemoryBackend::default(),
|
|
939
|
+
gate: BlockingGate::new(),
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
fn gate(&self) -> BlockingGate {
|
|
944
|
+
self.gate.clone()
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
impl Backend for BlockingBeginWriteBackend {
|
|
949
|
+
type Read<'a>
|
|
950
|
+
= InMemoryRead
|
|
951
|
+
where
|
|
952
|
+
Self: 'a;
|
|
953
|
+
|
|
954
|
+
type Write<'a>
|
|
955
|
+
= InMemoryWrite
|
|
956
|
+
where
|
|
957
|
+
Self: 'a;
|
|
958
|
+
|
|
959
|
+
fn capabilities(&self) -> BackendCapabilities {
|
|
960
|
+
self.inner.capabilities()
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
fn begin_read(&self, opts: ReadOptions) -> Result<Self::Read<'_>, BackendError> {
|
|
964
|
+
self.inner.begin_read(opts)
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
fn begin_write(&self, opts: WriteOptions) -> Result<Self::Write<'_>, BackendError> {
|
|
968
|
+
self.gate.maybe_block();
|
|
969
|
+
self.inner.begin_write(opts)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
fn durable_write_lock(&self) -> DurableWriteLock {
|
|
973
|
+
self.inner.durable_write_lock()
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
#[derive(Clone)]
|
|
978
|
+
struct BlockingGate {
|
|
979
|
+
state: std::sync::Arc<(Mutex<BlockingGateState>, Condvar)>,
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
impl BlockingGate {
|
|
983
|
+
fn new() -> Self {
|
|
984
|
+
Self {
|
|
985
|
+
state: std::sync::Arc::new((
|
|
986
|
+
Mutex::new(BlockingGateState::default()),
|
|
987
|
+
Condvar::new(),
|
|
988
|
+
)),
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
fn block_next(&self) {
|
|
993
|
+
let (lock, _) = &*self.state;
|
|
994
|
+
let mut state = lock.lock().expect("blocking gate lock should not poison");
|
|
995
|
+
state.block_next = true;
|
|
996
|
+
state.blocked = false;
|
|
997
|
+
state.released = false;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
fn maybe_block(&self) {
|
|
1001
|
+
let (lock, condvar) = &*self.state;
|
|
1002
|
+
let mut state = lock.lock().expect("blocking gate lock should not poison");
|
|
1003
|
+
if !state.block_next {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
state.block_next = false;
|
|
1007
|
+
state.blocked = true;
|
|
1008
|
+
condvar.notify_all();
|
|
1009
|
+
let deadline = Instant::now() + TEST_WAIT_TIMEOUT;
|
|
1010
|
+
while !state.released {
|
|
1011
|
+
let remaining = deadline.saturating_duration_since(Instant::now());
|
|
1012
|
+
assert!(
|
|
1013
|
+
!remaining.is_zero(),
|
|
1014
|
+
"timed out waiting for blocking gate release"
|
|
1015
|
+
);
|
|
1016
|
+
let (next_state, wait_result) = condvar
|
|
1017
|
+
.wait_timeout(state, remaining)
|
|
1018
|
+
.expect("blocking gate lock should not poison after wait");
|
|
1019
|
+
state = next_state;
|
|
1020
|
+
assert!(
|
|
1021
|
+
!wait_result.timed_out() || state.released,
|
|
1022
|
+
"timed out waiting for blocking gate release"
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
fn wait_until_blocked(&self) {
|
|
1028
|
+
let (lock, condvar) = &*self.state;
|
|
1029
|
+
let mut state = lock.lock().expect("blocking gate lock should not poison");
|
|
1030
|
+
let deadline = Instant::now() + TEST_WAIT_TIMEOUT;
|
|
1031
|
+
while !state.blocked {
|
|
1032
|
+
let remaining = deadline.saturating_duration_since(Instant::now());
|
|
1033
|
+
assert!(!remaining.is_zero(), "timed out waiting for blocking gate");
|
|
1034
|
+
let (next_state, wait_result) = condvar
|
|
1035
|
+
.wait_timeout(state, remaining)
|
|
1036
|
+
.expect("blocking gate lock should not poison after wait");
|
|
1037
|
+
state = next_state;
|
|
1038
|
+
assert!(
|
|
1039
|
+
!wait_result.timed_out() || state.blocked,
|
|
1040
|
+
"timed out waiting for blocking gate"
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
fn release(&self) {
|
|
1046
|
+
let (lock, condvar) = &*self.state;
|
|
1047
|
+
let mut state = lock.lock().expect("blocking gate lock should not poison");
|
|
1048
|
+
state.released = true;
|
|
1049
|
+
condvar.notify_all();
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
#[derive(Default)]
|
|
1054
|
+
struct BlockingGateState {
|
|
1055
|
+
block_next: bool,
|
|
1056
|
+
blocked: bool,
|
|
1057
|
+
released: bool,
|
|
1058
|
+
}
|
|
1059
|
+
}
|