Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
OpenDAS
dynamo
Commits
7731b024
Unverified
Commit
7731b024
authored
Oct 23, 2025
by
Graham King
Committed by
GitHub
Oct 23, 2025
Browse files
chore: Use KeyValueStoreManager instead of etcd::Client (#3822)
Signed-off-by:
Graham King
<
grahamk@nvidia.com
>
parent
6f9be594
Changes
36
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
214 additions
and
111 deletions
+214
-111
lib/runtime/src/distributed.rs
lib/runtime/src/distributed.rs
+13
-7
lib/runtime/src/health_check.rs
lib/runtime/src/health_check.rs
+1
-1
lib/runtime/src/instances.rs
lib/runtime/src/instances.rs
+2
-2
lib/runtime/src/lib.rs
lib/runtime/src/lib.rs
+3
-2
lib/runtime/src/logging.rs
lib/runtime/src/logging.rs
+1
-1
lib/runtime/src/pipeline/network/egress/push_router.rs
lib/runtime/src/pipeline/network/egress/push_router.rs
+3
-3
lib/runtime/src/pipeline/network/ingress/push_endpoint.rs
lib/runtime/src/pipeline/network/ingress/push_endpoint.rs
+1
-1
lib/runtime/src/storage/key_value_store.rs
lib/runtime/src/storage/key_value_store.rs
+112
-19
lib/runtime/src/storage/key_value_store/etcd.rs
lib/runtime/src/storage/key_value_store/etcd.rs
+15
-14
lib/runtime/src/storage/key_value_store/mem.rs
lib/runtime/src/storage/key_value_store/mem.rs
+8
-9
lib/runtime/src/storage/key_value_store/nats.rs
lib/runtime/src/storage/key_value_store/nats.rs
+6
-7
lib/runtime/src/transports/etcd.rs
lib/runtime/src/transports/etcd.rs
+25
-18
lib/runtime/src/transports/etcd/lease.rs
lib/runtime/src/transports/etcd/lease.rs
+14
-17
lib/runtime/src/transports/etcd/lock.rs
lib/runtime/src/transports/etcd/lock.rs
+2
-2
lib/runtime/src/utils/leader_worker_barrier.rs
lib/runtime/src/utils/leader_worker_barrier.rs
+6
-6
lib/runtime/src/utils/typed_prefix_watcher.rs
lib/runtime/src/utils/typed_prefix_watcher.rs
+2
-2
No files found.
lib/runtime/src/distributed.rs
View file @
7731b024
...
...
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
pub
use
crate
::
component
::
Component
;
use
crate
::
storage
::
key_value_store
::{
EtcdStore
,
KeyValueStore
,
MemoryStore
};
use
crate
::
storage
::
key_value_store
::{
EtcdStore
,
KeyValueStore
,
KeyValueStoreEnum
,
KeyValueStoreManager
,
MemoryStore
,
};
use
crate
::
transports
::
nats
::
DRTNatsClientPrometheusMetrics
;
use
crate
::{
ErrorContext
,
PrometheusUpdateCallback
,
...
...
@@ -46,12 +48,10 @@ impl DistributedRuntime {
let
runtime_clone
=
runtime
.clone
();
let
(
etcd_client
,
store
)
=
if
is_static
{
let
store
:
Arc
<
dyn
KeyValueStore
>
=
Arc
::
new
(
MemoryStore
::
new
());
(
None
,
store
)
(
None
,
KeyValueStoreManager
::
memory
())
}
else
{
let
etcd_client
=
etcd
::
Client
::
new
(
etcd_config
.clone
(),
runtime_clone
)
.await
?
;
let
store
:
Arc
<
dyn
KeyValueStore
>
=
Arc
::
new
(
EtcdStore
::
new
(
etcd_client
.clone
()));
let
store
=
KeyValueStoreManager
::
etcd
(
etcd_client
.clone
());
(
Some
(
etcd_client
),
store
)
};
...
...
@@ -278,10 +278,16 @@ impl DistributedRuntime {
self
.etcd_client
.clone
()
}
// Deprecated but our CI blocks us using the feature currently.
//#[deprecated(note = "Use KeyValueStoreManager via store(); this will be removed")]
pub
fn
deprecated_etcd_client
(
&
self
)
->
Option
<
etcd
::
Client
>
{
self
.etcd_client
.clone
()
}
/// An interface to store things. Will eventually replace `etcd_client`.
/// Currently does key-value, but will grow to include whatever we need to store.
pub
fn
store
(
&
self
)
->
Arc
<
dyn
KeyValueStore
>
{
self
.store
.clone
()
pub
fn
store
(
&
self
)
->
&
KeyValueStore
Manager
{
&
self
.store
}
pub
fn
child_token
(
&
self
)
->
CancellationToken
{
...
...
lib/runtime/src/health_check.rs
View file @
7731b024
...
...
@@ -484,7 +484,7 @@ mod integration_tests {
component
:
"test_component"
.to_string
(),
endpoint
:
format!
(
"test_endpoint_{}"
,
i
),
namespace
:
"test_namespace"
.to_string
(),
instance_id
:
i
as
i64
,
instance_id
:
i
,
transport
:
crate
::
component
::
TransportType
::
NatsTcp
(
endpoint
.clone
()),
},
payload
,
...
...
lib/runtime/src/instances.rs
View file @
7731b024
...
...
@@ -10,10 +10,10 @@
use
std
::
sync
::
Arc
;
use
crate
::
component
::{
INSTANCE_ROOT_PATH
,
Instance
};
use
crate
::
storage
::
key_value_store
::
KeyValueStore
;
use
crate
::
storage
::
key_value_store
::
{
KeyValueStore
,
KeyValueStoreManager
}
;
use
crate
::
transports
::
etcd
::
Client
as
EtcdClient
;
pub
async
fn
list_all_instances
(
client
:
Arc
<
dyn
KeyValueStore
>
)
->
anyhow
::
Result
<
Vec
<
Instance
>>
{
pub
async
fn
list_all_instances
(
client
:
&
KeyValueStore
Manager
)
->
anyhow
::
Result
<
Vec
<
Instance
>>
{
let
Some
(
bucket
)
=
client
.get_bucket
(
INSTANCE_ROOT_PATH
)
.await
?
else
{
return
Ok
(
vec!
[]);
};
...
...
lib/runtime/src/lib.rs
View file @
7731b024
...
...
@@ -52,7 +52,8 @@ pub use tokio_util::sync::CancellationToken;
pub
use
worker
::
Worker
;
use
crate
::{
metrics
::
prometheus_names
::
distributed_runtime
,
storage
::
key_value_store
::
KeyValueStore
,
metrics
::
prometheus_names
::
distributed_runtime
,
storage
::
key_value_store
::{
KeyValueStore
,
KeyValueStoreManager
},
};
use
component
::{
Endpoint
,
InstanceSource
};
...
...
@@ -188,7 +189,7 @@ pub struct DistributedRuntime {
// we might consider a unifed transport manager here
etcd_client
:
Option
<
transports
::
etcd
::
Client
>
,
nats_client
:
Option
<
transports
::
nats
::
Client
>
,
store
:
Arc
<
dyn
KeyValueStore
>
,
store
:
KeyValueStore
Manager
,
tcp_server
:
Arc
<
OnceCell
<
Arc
<
transports
::
tcp
::
server
::
TcpStreamServer
>>>
,
system_status_server
:
Arc
<
OnceLock
<
Arc
<
system_status_server
::
SystemStatusServerInfo
>>>
,
...
...
lib/runtime/src/logging.rs
View file @
7731b024
...
...
@@ -310,7 +310,7 @@ pub fn make_handle_payload_span(
component
:
&
str
,
endpoint
:
&
str
,
namespace
:
&
str
,
instance_id
:
i
64
,
instance_id
:
u
64
,
)
->
Span
{
let
(
otel_context
,
trace_id
,
parent_span_id
)
=
extract_otel_context_from_nats_headers
(
headers
);
let
trace_parent
=
TraceParent
::
from_headers
(
headers
);
...
...
lib/runtime/src/pipeline/network/egress/push_router.rs
View file @
7731b024
...
...
@@ -77,7 +77,7 @@ pub enum RouterMode {
#[default]
RoundRobin
,
Random
,
Direct
(
i
64
),
Direct
(
u
64
),
// Marker value, KV routing itself is in dynamo-llm
KV
,
}
...
...
@@ -181,7 +181,7 @@ where
pub
async
fn
direct
(
&
self
,
request
:
SingleIn
<
T
>
,
instance_id
:
i
64
,
instance_id
:
u
64
,
)
->
anyhow
::
Result
<
ManyOut
<
U
>>
{
let
found
=
self
.client
.instance_ids_avail
()
.contains
(
&
instance_id
);
...
...
@@ -206,7 +206,7 @@ where
async
fn
generate_with_fault_detection
(
&
self
,
instance_id
:
i
64
,
instance_id
:
u
64
,
request
:
SingleIn
<
T
>
,
)
->
anyhow
::
Result
<
ManyOut
<
U
>>
{
// Check if all workers are busy (only if busy threshold is set)
...
...
lib/runtime/src/pipeline/network/ingress/push_endpoint.rs
View file @
7731b024
...
...
@@ -39,7 +39,7 @@ impl PushEndpoint {
namespace
:
String
,
component_name
:
String
,
endpoint_name
:
String
,
instance_id
:
i
64
,
instance_id
:
u
64
,
system_health
:
Arc
<
Mutex
<
SystemHealth
>>
,
)
->
Result
<
()
>
{
let
mut
endpoint
=
endpoint
;
...
...
lib/runtime/src/storage/key_value_store.rs
View file @
7731b024
...
...
@@ -64,26 +64,115 @@ impl From<&Key> for String {
#[async_trait]
pub
trait
KeyValueStore
:
Send
+
Sync
{
type
Bucket
:
KeyValueBucket
+
Send
+
Sync
+
'static
;
async
fn
get_or_create_bucket
(
&
self
,
bucket_name
:
&
str
,
// auto-delete items older than this
ttl
:
Option
<
Duration
>
,
)
->
Result
<
Box
<
dyn
KeyValueBucket
>
,
StoreError
>
;
)
->
Result
<
Self
::
Bucket
,
StoreError
>
;
async
fn
get_bucket
(
&
self
,
bucket_name
:
&
str
)
->
Result
<
Option
<
Self
::
Bucket
>
,
StoreError
>
;
fn
connection_id
(
&
self
)
->
u64
;
}
#[allow(clippy::large_enum_variant)]
pub
enum
KeyValueStoreEnum
{
Memory
(
MemoryStore
),
Nats
(
NATSStore
),
Etcd
(
EtcdStore
),
}
impl
KeyValueStoreEnum
{
async
fn
get_or_create_bucket
(
&
self
,
bucket_name
:
&
str
,
// auto-delete items older than this
ttl
:
Option
<
Duration
>
,
)
->
Result
<
Box
<
dyn
KeyValueBucket
>
,
StoreError
>
{
use
KeyValueStoreEnum
::
*
;
Ok
(
match
self
{
Memory
(
x
)
=>
Box
::
new
(
x
.get_or_create_bucket
(
bucket_name
,
ttl
)
.await
?
),
Nats
(
x
)
=>
Box
::
new
(
x
.get_or_create_bucket
(
bucket_name
,
ttl
)
.await
?
),
Etcd
(
x
)
=>
Box
::
new
(
x
.get_or_create_bucket
(
bucket_name
,
ttl
)
.await
?
),
})
}
async
fn
get_bucket
(
&
self
,
bucket_name
:
&
str
,
)
->
Result
<
Option
<
Box
<
dyn
KeyValueBucket
>>
,
StoreError
>
;
)
->
Result
<
Option
<
Box
<
dyn
KeyValueBucket
>>
,
StoreError
>
{
use
KeyValueStoreEnum
::
*
;
let
maybe_bucket
:
Option
<
Box
<
dyn
KeyValueBucket
>>
=
match
self
{
Memory
(
x
)
=>
x
.get_bucket
(
bucket_name
)
.await
?
.map
(|
b
|
Box
::
new
(
b
)
as
Box
<
dyn
KeyValueBucket
>
),
Nats
(
x
)
=>
x
.get_bucket
(
bucket_name
)
.await
?
.map
(|
b
|
Box
::
new
(
b
)
as
Box
<
dyn
KeyValueBucket
>
),
Etcd
(
x
)
=>
x
.get_bucket
(
bucket_name
)
.await
?
.map
(|
b
|
Box
::
new
(
b
)
as
Box
<
dyn
KeyValueBucket
>
),
};
Ok
(
maybe_bucket
)
}
fn
connection_id
(
&
self
)
->
u64
;
fn
connection_id
(
&
self
)
->
u64
{
use
KeyValueStoreEnum
::
*
;
match
self
{
Memory
(
x
)
=>
x
.connection_id
(),
Etcd
(
x
)
=>
x
.connection_id
(),
Nats
(
x
)
=>
x
.connection_id
(),
}
}
}
pub
struct
KeyValueStoreManager
(
Box
<
dyn
KeyValueStore
>
);
#[derive(Clone)]
pub
struct
KeyValueStoreManager
(
Arc
<
KeyValueStoreEnum
>
);
impl
Default
for
KeyValueStoreManager
{
fn
default
()
->
Self
{
KeyValueStoreManager
::
memory
()
}
}
impl
KeyValueStoreManager
{
pub
fn
new
(
s
:
Box
<
dyn
KeyValueStore
>
)
->
KeyValueStoreManager
{
KeyValueStoreManager
(
s
)
/// In-memory KeyValueStoreManager for testing
pub
fn
memory
()
->
Self
{
Self
::
new
(
KeyValueStoreEnum
::
Memory
(
MemoryStore
::
new
()))
}
pub
fn
etcd
(
etcd_client
:
crate
::
transports
::
etcd
::
Client
)
->
Self
{
Self
::
new
(
KeyValueStoreEnum
::
Etcd
(
EtcdStore
::
new
(
etcd_client
)))
}
fn
new
(
s
:
KeyValueStoreEnum
)
->
KeyValueStoreManager
{
KeyValueStoreManager
(
Arc
::
new
(
s
))
}
pub
async
fn
get_or_create_bucket
(
&
self
,
bucket_name
:
&
str
,
// auto-delete items older than this
ttl
:
Option
<
Duration
>
,
)
->
Result
<
Box
<
dyn
KeyValueBucket
>
,
StoreError
>
{
self
.0
.get_or_create_bucket
(
bucket_name
,
ttl
)
.await
}
pub
async
fn
get_bucket
(
&
self
,
bucket_name
:
&
str
,
)
->
Result
<
Option
<
Box
<
dyn
KeyValueBucket
>>
,
StoreError
>
{
self
.0
.get_bucket
(
bucket_name
)
.await
}
pub
fn
connection_id
(
&
self
)
->
u64
{
self
.0
.connection_id
()
}
pub
async
fn
load
<
T
:
for
<
'a
>
Deserialize
<
'a
>>
(
...
...
@@ -95,17 +184,13 @@ impl KeyValueStoreManager {
// No bucket means no cards
return
Ok
(
None
);
};
match
bucket
.get
(
key
)
.await
{
Ok
(
Some
(
card_bytes
)
)
=>
{
Ok
(
match
bucket
.get
(
key
)
.await
?
{
Some
(
card_bytes
)
=>
{
let
card
:
T
=
serde_json
::
from_slice
(
card_bytes
.as_ref
())
?
;
Ok
(
Some
(
card
)
)
Some
(
card
)
}
Ok
(
None
)
=>
Ok
(
None
),
Err
(
err
)
=>
{
// TODO look at what errors NATS can give us and make more specific wrappers
Err
(
StoreError
::
NATSError
(
err
.to_string
()))
}
}
None
=>
None
,
})
}
/// Returns a receiver that will receive all the existing keys, and
...
...
@@ -115,6 +200,7 @@ impl KeyValueStoreManager {
self
:
Arc
<
Self
>
,
bucket_name
:
&
str
,
bucket_ttl
:
Option
<
Duration
>
,
cancel_token
:
CancellationToken
,
)
->
(
tokio
::
task
::
JoinHandle
<
Result
<
(),
StoreError
>>
,
tokio
::
sync
::
mpsc
::
UnboundedReceiver
<
T
>
,
...
...
@@ -136,7 +222,14 @@ impl KeyValueStoreManager {
}
// Now block waiting for new entries
while
let
Some
(
card_bytes
)
=
stream
.next
()
.await
{
loop
{
let
card_bytes
=
tokio
::
select!
{
_
=
cancel_token
.cancelled
()
=>
break
,
result
=
stream
.next
()
=>
match
result
{
Some
(
bytes
)
=>
bytes
,
None
=>
break
,
}
};
let
card
:
T
=
serde_json
::
from_slice
(
card_bytes
.as_ref
())
?
;
let
_
=
tx
.send
(
card
);
}
...
...
@@ -170,7 +263,7 @@ impl KeyValueStoreManager {
/// An online storage for key-value config values.
/// Usually backed by `nats-server`.
#[async_trait]
pub
trait
KeyValueBucket
:
Send
{
pub
trait
KeyValueBucket
:
Send
+
Sync
{
/// A bucket is a collection of key/value pairs.
/// Insert a value into a bucket, if it doesn't exist already
async
fn
insert
(
...
...
@@ -191,7 +284,7 @@ pub trait KeyValueBucket: Send {
/// such time.
async
fn
watch
(
&
self
,
)
->
Result
<
Pin
<
Box
<
dyn
futures
::
Stream
<
Item
=
bytes
::
Bytes
>
+
Send
+
'
life0
>>
,
StoreError
>
;
)
->
Result
<
Pin
<
Box
<
dyn
futures
::
Stream
<
Item
=
bytes
::
Bytes
>
+
Send
+
'
_
>>
,
StoreError
>
;
async
fn
entries
(
&
self
)
->
Result
<
HashMap
<
String
,
bytes
::
Bytes
>
,
StoreError
>
;
}
...
...
@@ -230,7 +323,7 @@ pub enum StoreError {
#[error(
"Internal etcd error: {0}"
)]
EtcdError
(
String
),
#[error(
"Key Value Error: {0} for bucket '{1}"
)]
#[error(
"Key Value Error: {0} for bucket '{1}
'
"
)]
KeyValueError
(
String
,
String
),
#[error(
"Error decoding bytes: {0}"
)]
...
...
lib/runtime/src/storage/key_value_store/etcd.rs
View file @
7731b024
...
...
@@ -25,31 +25,31 @@ impl EtcdStore {
#[async_trait]
impl
KeyValueStore
for
EtcdStore
{
type
Bucket
=
EtcdBucket
;
/// A "bucket" in etcd is a path prefix
async
fn
get_or_create_bucket
(
&
self
,
bucket_name
:
&
str
,
_
ttl
:
Option
<
Duration
>
,
// TODO ttl not used yet
)
->
Result
<
Box
<
dyn
KeyValueBucket
>
,
StoreError
>
{
Ok
(
self
.get_bucket
(
bucket_name
)
.await
?
.unwrap
())
)
->
Result
<
Self
::
Bucket
,
StoreError
>
{
Ok
(
EtcdBucket
{
client
:
self
.client
.clone
(),
bucket_name
:
bucket_name
.to_string
(),
})
}
/// A "bucket" in etcd is a path prefix. This creates an EtcdBucket object without doing
/// any network calls.
async
fn
get_bucket
(
&
self
,
bucket_name
:
&
str
,
)
->
Result
<
Option
<
Box
<
dyn
KeyValueBucket
>>
,
StoreError
>
{
Ok
(
Some
(
Box
::
new
(
EtcdBucket
{
async
fn
get_bucket
(
&
self
,
bucket_name
:
&
str
)
->
Result
<
Option
<
Self
::
Bucket
>
,
StoreError
>
{
Ok
(
Some
(
EtcdBucket
{
client
:
self
.client
.clone
(),
bucket_name
:
bucket_name
.to_string
(),
}))
)
}))
}
fn
connection_id
(
&
self
)
->
u64
{
// This conversion from i64 to u64 is safe because etcd lease IDs are u64 internally.
// They present as i64 because of the limitations of the etcd grpc/HTTP JSON API.
self
.client
.lease_id
()
as
u64
self
.client
.lease_id
()
}
}
...
...
@@ -108,7 +108,7 @@ impl KeyValueBucket for EtcdBucket {
{
let
k
=
make_key
(
&
self
.bucket_name
,
&
""
.into
());
tracing
::
trace!
(
"etcd watch: {k}"
);
let
(
_
watcher
,
mut
watch_stream
)
=
self
let
(
watcher
,
mut
watch_stream
)
=
self
.client
.etcd_client
()
.clone
()
...
...
@@ -116,6 +116,7 @@ impl KeyValueBucket for EtcdBucket {
.await
.map_err
(|
e
|
StoreError
::
EtcdError
(
e
.to_string
()))
?
;
let
output
=
stream!
{
let
_
watcher
=
watcher
;
// Keep it alive. Not sure if necessary.
while
let
Ok
(
Some
(
resp
))
=
watch_stream
.message
()
.await
{
for
e
in
resp
.events
()
{
if
matches!
(
e
.event_type
(),
EventType
::
Put
)
&&
e
.kv
()
.is_some
()
{
...
...
@@ -155,7 +156,7 @@ impl EtcdBucket {
tracing
::
trace!
(
"etcd create: {k}"
);
// Use atomic transaction to check and create in one operation
let
put_options
=
PutOptions
::
new
()
.with_lease
(
self
.client
.primary_lease
()
.id
());
let
put_options
=
PutOptions
::
new
()
.with_lease
(
self
.client
.primary_lease
()
.id
()
as
i64
);
// Build transaction that creates key only if it doesn't exist
let
txn
=
Txn
::
new
()
...
...
@@ -224,7 +225,7 @@ impl EtcdBucket {
}
let
put_options
=
PutOptions
::
new
()
.with_lease
(
self
.client
.primary_lease
()
.id
())
.with_lease
(
self
.client
.primary_lease
()
.id
()
as
i64
)
.with_prev_key
();
let
mut
put_resp
=
self
.client
...
...
lib/runtime/src/storage/key_value_store/mem.rs
View file @
7731b024
...
...
@@ -67,35 +67,34 @@ impl MemoryStore {
#[async_trait]
impl
KeyValueStore
for
MemoryStore
{
type
Bucket
=
MemoryBucketRef
;
async
fn
get_or_create_bucket
(
&
self
,
bucket_name
:
&
str
,
// MemoryStore doesn't respect TTL yet
_
ttl
:
Option
<
Duration
>
,
)
->
Result
<
Box
<
dyn
KeyValue
Bucket
>
,
StoreError
>
{
)
->
Result
<
Self
::
Bucket
,
StoreError
>
{
let
mut
locked_data
=
self
.inner.data
.lock
()
.await
;
// Ensure the bucket exists
locked_data
.entry
(
bucket_name
.to_string
())
.or_insert_with
(
MemoryBucket
::
new
);
// Return an object able to access it
Ok
(
Box
::
new
(
MemoryBucketRef
{
Ok
(
MemoryBucketRef
{
name
:
bucket_name
.to_string
(),
inner
:
self
.inner
.clone
(),
})
)
})
}
/// This operation cannot fail on MemoryStore. Always returns Ok.
async
fn
get_bucket
(
&
self
,
bucket_name
:
&
str
,
)
->
Result
<
Option
<
Box
<
dyn
KeyValueBucket
>>
,
StoreError
>
{
async
fn
get_bucket
(
&
self
,
bucket_name
:
&
str
)
->
Result
<
Option
<
Self
::
Bucket
>
,
StoreError
>
{
let
locked_data
=
self
.inner.data
.lock
()
.await
;
match
locked_data
.get
(
bucket_name
)
{
Some
(
_
)
=>
Ok
(
Some
(
Box
::
new
(
MemoryBucketRef
{
Some
(
_
)
=>
Ok
(
Some
(
MemoryBucketRef
{
name
:
bucket_name
.to_string
(),
inner
:
self
.inner
.clone
(),
}))
)
,
})),
None
=>
Ok
(
None
),
}
}
...
...
lib/runtime/src/storage/key_value_store/nats.rs
View file @
7731b024
...
...
@@ -23,25 +23,24 @@ pub struct NATSBucket {
#[async_trait]
impl
KeyValueStore
for
NATSStore
{
type
Bucket
=
NATSBucket
;
async
fn
get_or_create_bucket
(
&
self
,
bucket_name
:
&
str
,
ttl
:
Option
<
Duration
>
,
)
->
Result
<
Box
<
dyn
KeyValue
Bucket
>
,
StoreError
>
{
)
->
Result
<
Self
::
Bucket
,
StoreError
>
{
let
name
=
Slug
::
slugify
(
bucket_name
);
let
nats_store
=
self
.get_or_create_key_value
(
&
self
.endpoint.namespace
,
&
name
,
ttl
)
.await
?
;
Ok
(
Box
::
new
(
NATSBucket
{
nats_store
})
)
Ok
(
NATSBucket
{
nats_store
})
}
async
fn
get_bucket
(
&
self
,
bucket_name
:
&
str
,
)
->
Result
<
Option
<
Box
<
dyn
KeyValueBucket
>>
,
StoreError
>
{
async
fn
get_bucket
(
&
self
,
bucket_name
:
&
str
)
->
Result
<
Option
<
Self
::
Bucket
>
,
StoreError
>
{
let
name
=
Slug
::
slugify
(
bucket_name
);
match
self
.get_key_value
(
&
self
.endpoint.namespace
,
&
name
)
.await
?
{
Some
(
nats_store
)
=>
Ok
(
Some
(
Box
::
new
(
NATSBucket
{
nats_store
}))
)
,
Some
(
nats_store
)
=>
Ok
(
Some
(
NATSBucket
{
nats_store
})),
None
=>
Ok
(
None
),
}
}
...
...
lib/runtime/src/transports/etcd.rs
View file @
7731b024
...
...
@@ -34,15 +34,22 @@ use super::utils::build_in_runtime;
#[derive(Clone)]
pub
struct
Client
{
client
:
etcd_client
::
Client
,
primary_lease
:
i
64
,
primary_lease
:
u
64
,
runtime
:
Runtime
,
rt
:
Arc
<
tokio
::
runtime
::
Runtime
>
,
}
impl
std
::
fmt
::
Debug
for
Client
{
fn
fmt
(
&
self
,
f
:
&
mut
std
::
fmt
::
Formatter
<
'_
>
)
->
std
::
fmt
::
Result
{
write!
(
f
,
"etcd::Client primary_lease={}"
,
self
.primary_lease
)
}
}
#[derive(Debug,
Clone)]
pub
struct
Lease
{
/// ETCD lease ID
id
:
i64
,
/// Delivered as i64 by etcd because of documented gRPC limitations.
id
:
u64
,
/// [`CancellationToken`] associated with the lease
cancel_token
:
CancellationToken
,
...
...
@@ -50,7 +57,7 @@ pub struct Lease {
impl
Lease
{
/// Get the lease ID
pub
fn
id
(
&
self
)
->
i
64
{
pub
fn
id
(
&
self
)
->
u
64
{
self
.id
}
...
...
@@ -146,7 +153,7 @@ impl Client {
}
/// Get the primary lease ID.
pub
fn
lease_id
(
&
self
)
->
i
64
{
pub
fn
lease_id
(
&
self
)
->
u
64
{
self
.primary_lease
}
...
...
@@ -160,7 +167,7 @@ impl Client {
/// Create a [`Lease`] with a given time-to-live (TTL).
/// This [`Lease`] will be tied to the [`Runtime`], specifically a child [`CancellationToken`].
pub
async
fn
create_lease
(
&
self
,
ttl
:
i
64
)
->
Result
<
Lease
>
{
pub
async
fn
create_lease
(
&
self
,
ttl
:
u
64
)
->
Result
<
Lease
>
{
let
token
=
self
.runtime
.child_token
();
let
lease_client
=
self
.client
.lease_client
();
self
.rt
...
...
@@ -169,14 +176,14 @@ impl Client {
}
// Revoke an etcd lease given its lease id. A wrapper over etcd_client::LeaseClient::revoke
pub
async
fn
revoke_lease
(
&
self
,
lease_id
:
i
64
)
->
Result
<
()
>
{
pub
async
fn
revoke_lease
(
&
self
,
lease_id
:
u
64
)
->
Result
<
()
>
{
let
lease_client
=
self
.client
.lease_client
();
self
.rt
.spawn
(
revoke_lease
(
lease_client
,
lease_id
))
.await
?
}
pub
async
fn
kv_create
(
&
self
,
key
:
&
str
,
value
:
Vec
<
u8
>
,
lease_id
:
Option
<
i
64
>
)
->
Result
<
()
>
{
pub
async
fn
kv_create
(
&
self
,
key
:
&
str
,
value
:
Vec
<
u8
>
,
lease_id
:
Option
<
u
64
>
)
->
Result
<
()
>
{
let
id
=
lease_id
.unwrap_or
(
self
.lease_id
());
let
put_options
=
PutOptions
::
new
()
.with_lease
(
id
);
let
put_options
=
PutOptions
::
new
()
.with_lease
(
id
as
i64
);
// Build the transaction
let
txn
=
Txn
::
new
()
...
...
@@ -203,10 +210,10 @@ impl Client {
&
self
,
key
:
String
,
value
:
Vec
<
u8
>
,
lease_id
:
Option
<
i
64
>
,
lease_id
:
Option
<
u
64
>
,
)
->
Result
<
()
>
{
let
id
=
lease_id
.unwrap_or
(
self
.lease_id
());
let
put_options
=
PutOptions
::
new
()
.with_lease
(
id
);
let
put_options
=
PutOptions
::
new
()
.with_lease
(
id
as
i64
);
// Build the transaction that either creates the key if it doesn't exist,
// or validates the existing value matches what we expect
...
...
@@ -254,10 +261,10 @@ impl Client {
&
self
,
key
:
impl
AsRef
<
str
>
,
value
:
impl
AsRef
<
[
u8
]
>
,
lease_id
:
Option
<
i
64
>
,
lease_id
:
Option
<
u
64
>
,
)
->
Result
<
()
>
{
let
id
=
lease_id
.unwrap_or
(
self
.lease_id
());
let
put_options
=
PutOptions
::
new
()
.with_lease
(
id
);
let
put_options
=
PutOptions
::
new
()
.with_lease
(
id
as
i64
);
let
_
=
self
.client
.kv_client
()
...
...
@@ -274,7 +281,7 @@ impl Client {
)
->
Result
<
PutResponse
>
{
let
options
=
options
.unwrap_or_default
()
.with_lease
(
self
.primary_lease
()
.id
());
.with_lease
(
self
.primary_lease
()
.id
()
as
i64
);
self
.client
.kv_client
()
.put
(
key
.as_ref
(),
value
.as_ref
(),
Some
(
options
))
...
...
@@ -295,12 +302,12 @@ impl Client {
&
self
,
key
:
impl
Into
<
Vec
<
u8
>>
,
options
:
Option
<
DeleteOptions
>
,
)
->
Result
<
i
64
>
{
)
->
Result
<
u
64
>
{
self
.client
.kv_client
()
.delete
(
key
,
options
)
.await
.map
(|
del_response
|
del_response
.deleted
())
.map
(|
del_response
|
del_response
.deleted
()
as
u64
)
.map_err
(|
err
|
err
.into
())
}
...
...
@@ -319,11 +326,11 @@ impl Client {
pub
async
fn
lock
(
&
self
,
key
:
impl
Into
<
Vec
<
u8
>>
,
lease_id
:
Option
<
i
64
>
,
lease_id
:
Option
<
u
64
>
,
)
->
Result
<
LockResponse
>
{
let
mut
lock_client
=
self
.client
.lock_client
();
let
id
=
lease_id
.unwrap_or
(
self
.lease_id
());
let
options
=
LockOptions
::
new
()
.with_lease
(
id
);
let
options
=
LockOptions
::
new
()
.with_lease
(
id
as
i64
);
lock_client
.lock
(
key
,
Some
(
options
))
.await
...
...
@@ -629,7 +636,7 @@ impl KvCache {
}
/// Update a value in both the cache and etcd
pub
async
fn
put
(
&
self
,
key
:
&
str
,
value
:
Vec
<
u8
>
,
lease_id
:
Option
<
i
64
>
)
->
Result
<
()
>
{
pub
async
fn
put
(
&
self
,
key
:
&
str
,
value
:
Vec
<
u8
>
,
lease_id
:
Option
<
u
64
>
)
->
Result
<
()
>
{
let
full_key
=
format!
(
"{}{}"
,
self
.prefix
,
key
);
// Update etcd first
...
...
lib/runtime/src/transports/etcd/lease.rs
View file @
7731b024
...
...
@@ -6,13 +6,13 @@ use super::*;
/// Create a [`Lease`] with a given time-to-live (TTL) attached to the [`CancellationToken`].
pub
async
fn
create_lease
(
mut
lease_client
:
LeaseClient
,
ttl
:
i
64
,
ttl
:
u
64
,
token
:
CancellationToken
,
)
->
Result
<
Lease
>
{
let
lease
=
lease_client
.grant
(
ttl
,
None
)
.await
?
;
let
lease
=
lease_client
.grant
(
ttl
as
i64
,
None
)
.await
?
;
let
id
=
lease
.id
();
let
ttl
=
lease
.ttl
();
let
id
=
lease
.id
()
as
u64
;
let
ttl
=
lease
.ttl
()
as
u64
;
let
child
=
token
.child_token
();
let
clone
=
token
.clone
();
...
...
@@ -36,8 +36,8 @@ pub async fn create_lease(
}
/// Revoke a lease given its lease id. A wrapper over etcd_client::LeaseClient::revoke
pub
async
fn
revoke_lease
(
mut
lease_client
:
LeaseClient
,
lease_id
:
i
64
)
->
Result
<
()
>
{
match
lease_client
.revoke
(
lease_id
)
.await
{
pub
async
fn
revoke_lease
(
mut
lease_client
:
LeaseClient
,
lease_id
:
u
64
)
->
Result
<
()
>
{
match
lease_client
.revoke
(
lease_id
as
i64
)
.await
{
Ok
(
_
)
=>
Ok
(()),
Err
(
e
)
=>
{
tracing
::
warn!
(
"failed to revoke lease: {:?}"
,
e
);
...
...
@@ -52,15 +52,15 @@ pub async fn revoke_lease(mut lease_client: LeaseClient, lease_id: i64) -> Resul
/// If
pub
async
fn
keep_alive
(
client
:
LeaseClient
,
lease_id
:
i
64
,
ttl
:
i
64
,
lease_id
:
u
64
,
ttl
:
u
64
,
token
:
CancellationToken
,
)
->
Result
<
()
>
{
let
mut
ttl
=
ttl
;
let
mut
deadline
=
create_deadline
(
ttl
)
?
;
let
mut
client
=
client
;
let
(
mut
heartbeat_sender
,
mut
heartbeat_receiver
)
=
client
.keep_alive
(
lease_id
)
.await
?
;
let
(
mut
heartbeat_sender
,
mut
heartbeat_receiver
)
=
client
.keep_alive
(
lease_id
as
i64
)
.await
?
;
loop
{
// if the deadline is exceeded, then we have failed to issue a heartbeat in time
...
...
@@ -79,7 +79,7 @@ pub async fn keep_alive(
tracing
::
trace!
(
lease_id
,
"keep alive response received: {:?}"
,
resp
);
// update ttl and deadline
ttl
=
resp
.ttl
();
ttl
=
resp
.ttl
()
as
u64
;
deadline
=
create_deadline
(
ttl
)
?
;
if
resp
.ttl
()
==
0
{
...
...
@@ -91,11 +91,11 @@ pub async fn keep_alive(
_
=
token
.cancelled
()
=>
{
tracing
::
trace!
(
lease_id
,
"cancellation token triggered; revoking lease"
);
let
_
=
client
.revoke
(
lease_id
)
.await
?
;
let
_
=
client
.revoke
(
lease_id
as
i64
)
.await
?
;
return
Ok
(());
}
_
=
tokio
::
time
::
sleep
(
tokio
::
time
::
Duration
::
from_secs
(
ttl
as
u64
/
2
))
=>
{
_
=
tokio
::
time
::
sleep
(
tokio
::
time
::
Duration
::
from_secs
(
ttl
/
2
))
=>
{
tracing
::
trace!
(
lease_id
,
"sending keep alive"
);
// if we get a error issuing the heartbeat, set the ttl to 0
...
...
@@ -117,9 +117,6 @@ pub async fn keep_alive(
}
/// Create a deadline for a given time-to-live (TTL).
fn
create_deadline
(
ttl
:
i64
)
->
Result
<
std
::
time
::
Instant
>
{
if
ttl
<=
0
{
return
Err
(
error!
(
"invalid ttl: {}"
,
ttl
));
}
Ok
(
std
::
time
::
Instant
::
now
()
+
std
::
time
::
Duration
::
from_secs
(
ttl
as
u64
))
fn
create_deadline
(
ttl
:
u64
)
->
Result
<
std
::
time
::
Instant
>
{
Ok
(
std
::
time
::
Instant
::
now
()
+
std
::
time
::
Duration
::
from_secs
(
ttl
))
}
lib/runtime/src/transports/etcd/lock.rs
View file @
7731b024
...
...
@@ -112,7 +112,7 @@ impl DistributedRWLock {
)
->
Option
<
WriteLockGuard
<
'a
>>
{
let
write_key
=
format!
(
"v1/{}/writer"
,
self
.lock_prefix
);
let
lease_id
=
etcd_client
.lease_id
();
let
put_options
=
PutOptions
::
new
()
.with_lease
(
lease_id
);
let
put_options
=
PutOptions
::
new
()
.with_lease
(
lease_id
as
i64
);
// Step 1: Atomically create write lock only if it doesn't exist
let
txn
=
Txn
::
new
()
...
...
@@ -204,7 +204,7 @@ impl DistributedRWLock {
// Try to atomically acquire read lock
// The transaction checks that no writer exists and creates reader key atomically
let
put_options
=
PutOptions
::
new
()
.with_lease
(
lease_id
);
let
put_options
=
PutOptions
::
new
()
.with_lease
(
lease_id
as
i64
);
// Build atomic transaction: create reader key only if write_key doesn't exist
let
txn
=
Txn
::
new
()
...
...
lib/runtime/src/utils/leader_worker_barrier.rs
View file @
7731b024
...
...
@@ -85,7 +85,7 @@ async fn create_barrier_key<T: Serialize>(
client
:
&
Client
,
key
:
&
str
,
data
:
T
,
lease_id
:
Option
<
i
64
>
,
lease_id
:
Option
<
u
64
>
,
)
->
Result
<
(),
LeaderWorkerBarrierError
>
{
let
serialized_data
=
serde_json
::
to_vec
(
&
data
)
.map_err
(
LeaderWorkerBarrierError
::
SerdeError
)
?
;
...
...
@@ -151,7 +151,7 @@ impl<LeaderData: Serialize + DeserializeOwned, WorkerData: Serialize + Deseriali
data
:
&
LeaderData
,
)
->
anyhow
::
Result
<
HashMap
<
String
,
WorkerData
>
,
LeaderWorkerBarrierError
>
{
let
etcd_client
=
rt
.etcd_client
()
.
deprecated_
etcd_client
()
.ok_or
(
LeaderWorkerBarrierError
::
EtcdClientNotFound
)
?
;
let
lease_id
=
etcd_client
.lease_id
();
...
...
@@ -178,7 +178,7 @@ impl<LeaderData: Serialize + DeserializeOwned, WorkerData: Serialize + Deseriali
&
self
,
client
:
&
Client
,
data
:
&
LeaderData
,
lease_id
:
i
64
,
lease_id
:
u
64
,
)
->
Result
<
(),
LeaderWorkerBarrierError
>
{
let
key
=
barrier_key
(
&
self
.barrier_id
,
BARRIER_DATA
);
create_barrier_key
(
client
,
&
key
,
data
,
Some
(
lease_id
))
.await
...
...
@@ -197,7 +197,7 @@ impl<LeaderData: Serialize + DeserializeOwned, WorkerData: Serialize + Deseriali
&
self
,
client
:
&
Client
,
worker_result
:
&
Result
<
HashMap
<
String
,
WorkerData
>
,
LeaderWorkerBarrierError
>
,
lease_id
:
i
64
,
lease_id
:
u
64
,
)
->
Result
<
(),
LeaderWorkerBarrierError
>
{
if
let
Ok
(
worker_result
)
=
worker_result
{
let
key
=
barrier_key
(
&
self
.barrier_id
,
BARRIER_COMPLETE
);
...
...
@@ -245,7 +245,7 @@ impl<LeaderData: Serialize + DeserializeOwned, WorkerData: Serialize + Deseriali
data
:
&
WorkerData
,
)
->
anyhow
::
Result
<
LeaderData
,
LeaderWorkerBarrierError
>
{
let
etcd_client
=
rt
.etcd_client
()
.
deprecated_
etcd_client
()
.ok_or
(
LeaderWorkerBarrierError
::
EtcdClientNotFound
)
?
;
let
lease_id
=
etcd_client
.lease_id
();
...
...
@@ -284,7 +284,7 @@ impl<LeaderData: Serialize + DeserializeOwned, WorkerData: Serialize + Deseriali
&
self
,
client
:
&
Client
,
data
:
&
WorkerData
,
lease_id
:
i
64
,
lease_id
:
u
64
,
)
->
Result
<
String
,
LeaderWorkerBarrierError
>
{
let
key
=
barrier_key
(
&
self
.barrier_id
,
...
...
lib/runtime/src/utils/typed_prefix_watcher.rs
View file @
7731b024
...
...
@@ -208,8 +208,8 @@ pub mod key_extractors {
use
etcd_client
::
KeyValue
;
/// Extract the lease ID as the key
pub
fn
lease_id
(
kv
:
&
KeyValue
)
->
Option
<
i
64
>
{
Some
(
kv
.lease
())
pub
fn
lease_id
(
kv
:
&
KeyValue
)
->
Option
<
u
64
>
{
Some
(
kv
.lease
()
as
u64
)
}
/// Extract the key as a string (without prefix)
...
...
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment