@lix-js/sdk 0.6.0-preview.3 → 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 +105 -65
- package/dist/engine-wasm/index.js +4 -4
- package/dist/engine-wasm/wasm/lix_engine.d.ts +30 -6
- package/dist/engine-wasm/wasm/lix_engine.js +187 -117
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +14 -8
- package/dist/generated/builtin-schemas.d.ts +69 -69
- package/dist/generated/builtin-schemas.js +94 -94
- package/dist/open-lix.d.ts +42 -28
- package/dist/open-lix.js +49 -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 +819 -124
- package/dist-engine-src/src/session/create_branch.rs +94 -0
- package/dist-engine-src/src/session/execute.rs +260 -57
- 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 +19 -16
- package/dist-engine-src/src/session/switch_branch.rs +113 -0
- package/dist-engine-src/src/session/transaction.rs +557 -0
- package/dist-engine-src/src/sql2/bind/classify.rs +102 -0
- 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} +98 -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 +4 -5
- 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 +30 -24
- 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 -109
- package/dist-engine-src/src/sql2/classify.rs +0 -182
- package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
- package/dist-engine-src/src/sql2/execute.rs +0 -3440
- 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 -166
- package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -25
- 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
|
-
|
|
30
|
+
use super::transaction::{SessionOperationGuard, SessionTransactionManager, SessionWriteLease};
|
|
31
|
+
|
|
32
|
+
pub(crate) const WORKSPACE_BRANCH_KEY: &str = "lix_workspace_branch_id";
|
|
29
33
|
|
|
30
34
|
#[derive(Clone)]
|
|
31
35
|
pub(crate) enum SessionMode {
|
|
32
|
-
Pinned {
|
|
36
|
+
Pinned { branch_id: String },
|
|
33
37
|
Workspace,
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
/// Session-context state for engine execution.
|
|
37
41
|
///
|
|
38
|
-
/// A session context pins the active
|
|
39
|
-
/// services.
|
|
40
|
-
///
|
|
41
|
-
///
|
|
42
|
-
///
|
|
43
|
-
/// through `SessionContext::with_write_transaction`. Reads that influence writes
|
|
44
|
-
/// are only available from that transaction capability, not from session-level
|
|
45
|
-
/// helpers.
|
|
42
|
+
/// A session context pins the active branch selector and shared execution
|
|
43
|
+
/// services. Parent-handle `execute(...)` runs as an implicit single-statement
|
|
44
|
+
/// transaction. Explicit transactions hold the session execution lease until
|
|
45
|
+
/// commit or rollback, so all SQL during that window must run through the
|
|
46
|
+
/// transaction handle.
|
|
46
47
|
#[derive(Clone)]
|
|
47
|
-
pub struct SessionContext {
|
|
48
|
+
pub struct SessionContext<B: StorageBackend = InMemoryStorageBackend> {
|
|
48
49
|
pub(super) mode: SessionMode,
|
|
49
|
-
pub(super) storage: StorageContext
|
|
50
|
+
pub(super) storage: StorageContext<B>,
|
|
50
51
|
pub(super) live_state: Arc<LiveStateContext>,
|
|
51
52
|
pub(super) tracked_state: Arc<TrackedStateContext>,
|
|
52
53
|
pub(super) binary_cas: Arc<BinaryCasContext>,
|
|
53
|
-
pub(super)
|
|
54
|
-
pub(super) version_ctx: Arc<VersionContext>,
|
|
54
|
+
pub(super) branch_ctx: Arc<BranchContext>,
|
|
55
55
|
pub(super) catalog_context: Arc<CatalogContext>,
|
|
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,71 +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
|
-
|
|
131
|
+
write_lock,
|
|
132
|
+
SessionTransactionManager::new(),
|
|
127
133
|
)
|
|
128
134
|
}
|
|
129
135
|
|
|
130
|
-
pub(super) fn
|
|
136
|
+
pub(super) fn new_with_transaction_manager(
|
|
131
137
|
mode: SessionMode,
|
|
132
|
-
storage: StorageContext
|
|
138
|
+
storage: StorageContext<B>,
|
|
133
139
|
live_state: Arc<LiveStateContext>,
|
|
134
140
|
tracked_state: Arc<TrackedStateContext>,
|
|
135
141
|
binary_cas: Arc<BinaryCasContext>,
|
|
136
|
-
|
|
137
|
-
version_ctx: Arc<VersionContext>,
|
|
142
|
+
branch_ctx: Arc<BranchContext>,
|
|
138
143
|
catalog_context: Arc<CatalogContext>,
|
|
139
|
-
|
|
144
|
+
write_lock: DurableWriteLock,
|
|
145
|
+
transaction_manager: SessionTransactionManager,
|
|
140
146
|
) -> Self {
|
|
141
147
|
Self {
|
|
142
148
|
mode,
|
|
@@ -144,167 +150,239 @@ impl SessionContext {
|
|
|
144
150
|
live_state,
|
|
145
151
|
tracked_state,
|
|
146
152
|
binary_cas,
|
|
147
|
-
|
|
148
|
-
version_ctx,
|
|
153
|
+
branch_ctx,
|
|
149
154
|
catalog_context,
|
|
150
|
-
|
|
155
|
+
write_lock,
|
|
156
|
+
transaction_manager,
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
|
|
154
160
|
/// Releases this logical session handle. This is a lifecycle boundary only:
|
|
155
161
|
/// successful writes are committed before their operation returns.
|
|
156
162
|
pub async fn close(&self) -> Result<(), LixError> {
|
|
157
|
-
self.
|
|
158
|
-
Ok(())
|
|
163
|
+
self.transaction_manager.close().await
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
pub fn is_closed(&self) -> bool {
|
|
162
|
-
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()
|
|
163
178
|
}
|
|
164
179
|
|
|
165
|
-
|
|
166
|
-
|
|
180
|
+
#[cfg(test)]
|
|
181
|
+
pub(crate) fn active_transaction_for_test(&self) -> bool {
|
|
182
|
+
self.transaction_manager.active_transaction_for_test()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
pub(super) fn transaction_manager(&self) -> SessionTransactionManager {
|
|
186
|
+
self.transaction_manager.clone()
|
|
167
187
|
}
|
|
168
188
|
|
|
169
189
|
pub(crate) fn ensure_open(&self) -> Result<(), LixError> {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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)
|
|
174
230
|
}
|
|
175
231
|
|
|
176
|
-
/// Resolves the
|
|
232
|
+
/// Resolves the branch this session should operate on right now.
|
|
177
233
|
///
|
|
178
|
-
/// 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
|
|
179
235
|
/// through the transaction capability so the read is scoped to the
|
|
180
236
|
/// same backend transaction as the writes it influences.
|
|
181
237
|
///
|
|
182
|
-
/// Pinned sessions are pure in-memory views over one
|
|
238
|
+
/// Pinned sessions are pure in-memory views over one branch. Workspace
|
|
183
239
|
/// sessions read the shared workspace selector from untracked global
|
|
184
240
|
/// `lix_key_value` state so multiple open app sessions can observe the same
|
|
185
|
-
/// active workspace
|
|
186
|
-
pub async fn
|
|
187
|
-
let
|
|
188
|
-
let
|
|
189
|
-
|
|
190
|
-
.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;
|
|
191
246
|
match result {
|
|
192
|
-
Ok(
|
|
193
|
-
|
|
194
|
-
Ok(version_id)
|
|
195
|
-
}
|
|
196
|
-
Err(error) => {
|
|
197
|
-
let _ = transaction.rollback().await;
|
|
198
|
-
Err(error)
|
|
199
|
-
}
|
|
247
|
+
Ok(branch_id) => Ok(branch_id),
|
|
248
|
+
Err(error) => Err(error),
|
|
200
249
|
}
|
|
201
250
|
}
|
|
202
251
|
|
|
203
|
-
pub(super) async fn
|
|
252
|
+
pub(super) async fn active_branch_id_from_reader<S>(
|
|
204
253
|
&self,
|
|
205
|
-
reader: &
|
|
254
|
+
reader: &S,
|
|
206
255
|
) -> Result<String, LixError>
|
|
207
256
|
where
|
|
208
|
-
S:
|
|
257
|
+
S: StorageRead + Send + Sync + ?Sized,
|
|
209
258
|
{
|
|
210
259
|
self.ensure_open()?;
|
|
211
260
|
match &self.mode {
|
|
212
|
-
SessionMode::Pinned {
|
|
213
|
-
SessionMode::Workspace => self.
|
|
261
|
+
SessionMode::Pinned { branch_id } => Ok(branch_id.clone()),
|
|
262
|
+
SessionMode::Workspace => self.load_workspace_branch_id(reader).await,
|
|
214
263
|
}
|
|
215
264
|
}
|
|
216
265
|
|
|
217
|
-
async fn
|
|
266
|
+
async fn load_workspace_branch_id<S>(&self, reader: &S) -> Result<String, LixError>
|
|
218
267
|
where
|
|
219
|
-
S:
|
|
268
|
+
S: StorageRead + Send + Sync + ?Sized,
|
|
220
269
|
{
|
|
221
270
|
let row = self
|
|
222
271
|
.live_state
|
|
223
|
-
.reader(
|
|
272
|
+
.reader(reader)
|
|
224
273
|
.load_row(&LiveStateRowRequest {
|
|
225
274
|
schema_key: "lix_key_value".to_string(),
|
|
226
|
-
|
|
227
|
-
|
|
275
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
276
|
+
entity_pk: EntityPk::single(WORKSPACE_BRANCH_KEY),
|
|
228
277
|
file_id: NullableKeyFilter::Null,
|
|
229
278
|
})
|
|
230
279
|
.await?
|
|
231
280
|
.ok_or_else(|| {
|
|
232
281
|
LixError::new(
|
|
233
282
|
"LIX_ERROR_UNKNOWN",
|
|
234
|
-
"workspace
|
|
283
|
+
"workspace branch selector is missing lix_key_value:lix_workspace_branch_id",
|
|
235
284
|
)
|
|
236
285
|
})?;
|
|
237
286
|
let snapshot_content = row.snapshot_content.as_deref().ok_or_else(|| {
|
|
238
287
|
LixError::new(
|
|
239
288
|
"LIX_ERROR_UNKNOWN",
|
|
240
|
-
"workspace
|
|
289
|
+
"workspace branch selector is missing snapshot_content",
|
|
241
290
|
)
|
|
242
291
|
})?;
|
|
243
292
|
let snapshot = serde_json::from_str::<JsonValue>(snapshot_content).map_err(|error| {
|
|
244
293
|
LixError::new(
|
|
245
294
|
"LIX_ERROR_UNKNOWN",
|
|
246
|
-
format!("workspace
|
|
295
|
+
format!("workspace branch selector snapshot is invalid JSON: {error}"),
|
|
247
296
|
)
|
|
248
297
|
})?;
|
|
249
|
-
let
|
|
298
|
+
let branch_id = snapshot
|
|
250
299
|
.get("value")
|
|
251
300
|
.and_then(JsonValue::as_str)
|
|
252
301
|
.filter(|value| !value.is_empty())
|
|
253
302
|
.ok_or_else(|| {
|
|
254
303
|
LixError::new(
|
|
255
304
|
"LIX_ERROR_UNKNOWN",
|
|
256
|
-
"workspace
|
|
305
|
+
"workspace branch selector value must be a non-empty string",
|
|
257
306
|
)
|
|
258
307
|
})?
|
|
259
308
|
.to_string();
|
|
260
309
|
|
|
261
|
-
let
|
|
262
|
-
|
|
310
|
+
let branch_ref = self.branch_ctx.ref_reader(reader);
|
|
311
|
+
BranchLifecycle::new(&branch_ref)
|
|
263
312
|
.require_existing_ref(
|
|
264
|
-
&
|
|
265
|
-
|
|
266
|
-
|
|
313
|
+
&branch_id,
|
|
314
|
+
BranchOperation::LoadWorkspaceSelector,
|
|
315
|
+
BranchReferenceRole::WorkspaceSelector,
|
|
267
316
|
)
|
|
268
317
|
.await?;
|
|
269
318
|
|
|
270
|
-
Ok(
|
|
319
|
+
Ok(branch_id)
|
|
271
320
|
}
|
|
272
321
|
|
|
273
322
|
pub(crate) async fn with_write_transaction<T, F>(&self, f: F) -> Result<T, LixError>
|
|
274
323
|
where
|
|
275
324
|
F: for<'tx> FnOnce(
|
|
276
|
-
&'tx mut Transaction
|
|
325
|
+
&'tx mut Transaction<B>,
|
|
277
326
|
) -> Pin<Box<dyn Future<Output = Result<T, LixError>> + 'tx>>,
|
|
278
327
|
{
|
|
279
328
|
self.ensure_open()?;
|
|
329
|
+
let write_access = self.begin_session_write_access().await?;
|
|
330
|
+
self.with_write_transaction_reserved(write_access, f).await
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
pub(super) async fn with_write_transaction_reserved<T, F>(
|
|
334
|
+
&self,
|
|
335
|
+
_write_access: SessionWriteAccess,
|
|
336
|
+
f: F,
|
|
337
|
+
) -> Result<T, LixError>
|
|
338
|
+
where
|
|
339
|
+
F: for<'tx> FnOnce(
|
|
340
|
+
&'tx mut Transaction<B>,
|
|
341
|
+
) -> Pin<Box<dyn Future<Output = Result<T, LixError>> + 'tx>>,
|
|
342
|
+
{
|
|
280
343
|
let opened = open_transaction(
|
|
281
344
|
&self.mode,
|
|
282
345
|
self.storage.clone(),
|
|
283
346
|
Arc::clone(&self.live_state),
|
|
284
347
|
Arc::clone(&self.tracked_state),
|
|
285
348
|
Arc::clone(&self.binary_cas),
|
|
286
|
-
Arc::clone(&self.
|
|
287
|
-
Arc::clone(&self.version_ctx),
|
|
349
|
+
Arc::clone(&self.branch_ctx),
|
|
288
350
|
Arc::clone(&self.catalog_context),
|
|
289
351
|
)
|
|
290
352
|
.await?;
|
|
353
|
+
self.ensure_open()?;
|
|
291
354
|
let mut transaction = opened.transaction;
|
|
355
|
+
transaction.attach_commit_boundary(self.transaction_commit_boundary());
|
|
292
356
|
let runtime_functions = opened.runtime_functions;
|
|
293
357
|
|
|
294
358
|
match f(&mut transaction).await {
|
|
295
359
|
Ok(value) => {
|
|
360
|
+
self.ensure_open()?;
|
|
296
361
|
transaction.commit(&runtime_functions).await?;
|
|
297
362
|
Ok(value)
|
|
298
363
|
}
|
|
299
|
-
Err(error) =>
|
|
300
|
-
let _ = transaction.rollback().await;
|
|
301
|
-
Err(error)
|
|
302
|
-
}
|
|
364
|
+
Err(error) => Err(error),
|
|
303
365
|
}
|
|
304
366
|
}
|
|
367
|
+
|
|
368
|
+
#[cfg(test)]
|
|
369
|
+
pub(super) fn begin_commit(&self) -> crate::transaction::CommitBoundaryGuard {
|
|
370
|
+
self.transaction_manager.begin_commit()
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
pub(super) fn transaction_commit_boundary(
|
|
374
|
+
&self,
|
|
375
|
+
) -> crate::transaction::TransactionCommitBoundary {
|
|
376
|
+
self.transaction_manager.transaction_commit_boundary()
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
pub(super) struct SessionWriteAccess {
|
|
381
|
+
_write_guard: DurableWriteGuard,
|
|
382
|
+
_write_lease: SessionWriteLease,
|
|
305
383
|
}
|
|
306
384
|
|
|
307
|
-
fn closed_error() -> LixError {
|
|
385
|
+
pub(super) fn closed_error() -> LixError {
|
|
308
386
|
LixError::new(LixError::CODE_CLOSED, "Lix handle is closed")
|
|
309
387
|
.with_hint("Open a new Lix handle before calling this method.")
|
|
310
388
|
}
|
|
@@ -313,32 +391,40 @@ fn closed_error() -> LixError {
|
|
|
313
391
|
///
|
|
314
392
|
/// Write statements re-plan against `Transaction`; this context intentionally
|
|
315
393
|
/// has no write stager.
|
|
316
|
-
pub(super) struct SessionSqlExecutionContext<'a> {
|
|
317
|
-
pub(super)
|
|
318
|
-
pub(super) read_store:
|
|
319
|
-
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>,
|
|
320
397
|
pub(super) live_state: Arc<LiveStateContext>,
|
|
321
398
|
pub(super) binary_cas: Arc<BinaryCasContext>,
|
|
322
|
-
pub(super)
|
|
323
|
-
pub(super) version_ctx: Arc<VersionContext>,
|
|
399
|
+
pub(super) branch_ctx: Arc<BranchContext>,
|
|
324
400
|
pub(super) visible_schemas: Vec<JsonValue>,
|
|
325
401
|
pub(super) functions: FunctionProviderHandle,
|
|
326
402
|
}
|
|
327
403
|
|
|
328
|
-
impl SqlExecutionContext for SessionSqlExecutionContext<'_>
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
331
412
|
}
|
|
332
413
|
|
|
333
414
|
fn live_state(&self) -> Arc<dyn LiveStateReader> {
|
|
334
415
|
Arc::new(self.live_state.reader(self.read_store.clone())) as Arc<dyn LiveStateReader>
|
|
335
416
|
}
|
|
336
417
|
|
|
337
|
-
fn
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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()),
|
|
342
428
|
}
|
|
343
429
|
}
|
|
344
430
|
|
|
@@ -346,8 +432,8 @@ impl SqlExecutionContext for SessionSqlExecutionContext<'_> {
|
|
|
346
432
|
Box::new(CommitGraphContext::new().reader(self.read_store.clone()))
|
|
347
433
|
}
|
|
348
434
|
|
|
349
|
-
fn
|
|
350
|
-
Arc::new(self.
|
|
435
|
+
fn branch_ref(&self) -> Arc<dyn BranchRefReader> {
|
|
436
|
+
Arc::new(self.branch_ctx.ref_reader(self.read_store.clone()))
|
|
351
437
|
}
|
|
352
438
|
|
|
353
439
|
fn functions(&self) -> FunctionProviderHandle {
|
|
@@ -362,3 +448,612 @@ impl SqlExecutionContext for SessionSqlExecutionContext<'_> {
|
|
|
362
448
|
Ok(self.visible_schemas.clone())
|
|
363
449
|
}
|
|
364
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
|
+
}
|