etcd_client/rpc/
lock.rs

1//! Etcd Lock RPC.
2
3use super::pb::v3lockpb;
4use crate::auth::AuthService;
5use crate::error::Result;
6use crate::intercept::InterceptedChannel;
7use crate::rpc::ResponseHeader;
8use http::HeaderValue;
9use std::sync::{Arc, RwLock};
10use tonic::{IntoRequest, Request};
11use v3lockpb::lock_client::LockClient as PbLockClient;
12use v3lockpb::{
13    LockRequest as PbLockRequest, LockResponse as PbLockResponse, UnlockRequest as PbUnlockRequest,
14    UnlockResponse as PbUnlockResponse,
15};
16
17/// Client for Lock operations.
18#[repr(transparent)]
19#[derive(Clone)]
20pub struct LockClient {
21    inner: PbLockClient<AuthService<InterceptedChannel>>,
22}
23
24impl LockClient {
25    /// Creates a lock client.
26    #[inline]
27    pub(crate) fn new(
28        channel: InterceptedChannel,
29        auth_token: Arc<RwLock<Option<HeaderValue>>>,
30    ) -> Self {
31        let inner = PbLockClient::new(AuthService::new(channel, auth_token));
32        Self { inner }
33    }
34
35    /// Acquires a distributed shared lock on a given named lock.
36    /// On success, it will return a unique key that exists so long as the
37    /// lock is held by the caller. This key can be used in conjunction with
38    /// transactions to safely ensure updates to etcd only occur while holding
39    /// lock ownership. The lock is held until Unlock is called on the key or the
40    /// lease associate with the owner expires.
41    #[inline]
42    pub async fn lock(
43        &mut self,
44        name: impl Into<Vec<u8>>,
45        options: Option<LockOptions>,
46    ) -> Result<LockResponse> {
47        let resp = self
48            .inner
49            .lock(options.unwrap_or_default().with_name(name))
50            .await?
51            .into_inner();
52        Ok(LockResponse::new(resp))
53    }
54
55    /// Takes a key returned by Lock and releases the hold on lock. The
56    /// next Lock caller waiting for the lock will then be woken up and given
57    /// ownership of the lock.
58    #[inline]
59    pub async fn unlock(&mut self, key: impl Into<Vec<u8>>) -> Result<UnlockResponse> {
60        let resp = self
61            .inner
62            .unlock(UnlockOptions::new().with_key(key))
63            .await?
64            .into_inner();
65        Ok(UnlockResponse::new(resp))
66    }
67}
68
69/// Options for `Lock` operation.
70#[derive(Debug, Default, Clone)]
71#[repr(transparent)]
72pub struct LockOptions(PbLockRequest);
73
74impl LockOptions {
75    /// name is the identifier for the distributed shared lock to be acquired.
76    #[inline]
77    fn with_name(mut self, name: impl Into<Vec<u8>>) -> Self {
78        self.0.name = name.into();
79        self
80    }
81
82    /// Creates a `LockOptions`.
83    #[inline]
84    pub const fn new() -> Self {
85        Self(PbLockRequest {
86            name: Vec::new(),
87            lease: 0,
88        })
89    }
90
91    /// `lease` is the ID of the lease that will be attached to ownership of the
92    /// lock. If the lease expires or is revoked and currently holds the lock,
93    /// the lock is automatically released. Calls to Lock with the same lease will
94    /// be treated as a single acquisition; locking twice with the same lease is a
95    /// no-op.
96    #[inline]
97    pub const fn with_lease(mut self, lease: i64) -> Self {
98        self.0.lease = lease;
99        self
100    }
101}
102
103impl From<LockOptions> for PbLockRequest {
104    #[inline]
105    fn from(options: LockOptions) -> Self {
106        options.0
107    }
108}
109
110impl IntoRequest<PbLockRequest> for LockOptions {
111    #[inline]
112    fn into_request(self) -> Request<PbLockRequest> {
113        Request::new(self.into())
114    }
115}
116
117/// Response for `Lock` operation.
118#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
119#[derive(Debug, Default, Clone)]
120#[repr(transparent)]
121pub struct LockResponse(PbLockResponse);
122
123impl LockResponse {
124    /// Create a new `LockResponse` from pb lock response.
125    #[inline]
126    const fn new(resp: PbLockResponse) -> Self {
127        Self(resp)
128    }
129
130    /// Get response header.
131    #[inline]
132    pub fn header(&self) -> Option<&ResponseHeader> {
133        self.0.header.as_ref().map(From::from)
134    }
135
136    /// Takes the header out of the response, leaving a [`None`] in its place.
137    #[inline]
138    pub fn take_header(&mut self) -> Option<ResponseHeader> {
139        self.0.header.take().map(ResponseHeader::new)
140    }
141
142    /// A key that will exist on etcd for the duration that the Lock caller
143    /// owns the lock. Users should not modify this key or the lock may exhibit
144    /// undefined behavior.
145    #[inline]
146    pub fn key(&self) -> &[u8] {
147        &self.0.key
148    }
149}
150
151/// Options for `Unlock` operation.
152#[derive(Debug, Default, Clone)]
153#[repr(transparent)]
154pub struct UnlockOptions(PbUnlockRequest);
155
156impl UnlockOptions {
157    /// key is the lock ownership key granted by Lock.
158    #[inline]
159    fn with_key(mut self, key: impl Into<Vec<u8>>) -> Self {
160        self.0.key = key.into();
161        self
162    }
163
164    /// Creates a `UnlockOptions`.
165    #[inline]
166    pub const fn new() -> Self {
167        Self(PbUnlockRequest { key: Vec::new() })
168    }
169}
170
171impl From<UnlockOptions> for PbUnlockRequest {
172    #[inline]
173    fn from(options: UnlockOptions) -> Self {
174        options.0
175    }
176}
177
178impl IntoRequest<PbUnlockRequest> for UnlockOptions {
179    #[inline]
180    fn into_request(self) -> Request<PbUnlockRequest> {
181        Request::new(self.into())
182    }
183}
184
185/// Response for `Unlock` operation.
186#[cfg_attr(feature = "pub-response-field", visible::StructFields(pub))]
187#[derive(Debug, Default, Clone)]
188#[repr(transparent)]
189pub struct UnlockResponse(PbUnlockResponse);
190
191impl UnlockResponse {
192    /// Create a new `UnlockResponse` from pb unlock response.
193    #[inline]
194    const fn new(resp: PbUnlockResponse) -> Self {
195        Self(resp)
196    }
197
198    /// Get response header.
199    #[inline]
200    pub fn header(&self) -> Option<&ResponseHeader> {
201        self.0.header.as_ref().map(From::from)
202    }
203
204    /// Takes the header out of the response, leaving a [`None`] in its place.
205    #[inline]
206    pub fn take_header(&mut self) -> Option<ResponseHeader> {
207        self.0.header.take().map(ResponseHeader::new)
208    }
209}