etcd_client/rpc/
lock.rs

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