etcd_client/rpc/
lock.rs

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