Skip to main content

glide/sync/
mod.rs

1// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2//! Blocking (synchronous) clients.
3//!
4//! Mirrors Python `glide-sync`. [`SyncGlideClient`] / [`SyncGlideClusterClient`]
5//! wrap the async clients and drive them on a shared, process-wide Tokio runtime.
6//!
7//! Every async command is reachable from sync code via [`SyncGlideClient::run`]
8//! (and the cluster equivalent), and the most common commands also have direct
9//! blocking methods.
10//!
11//! The blocking methods here must not be called from within an async context
12//! (a running Tokio runtime) — doing so panics with tokio's "cannot block the
13//! current thread from within a runtime".
14
15use crate::AsyncTypedCommands;
16use crate::client::{GlideClient, GlideClusterClient};
17use crate::config::{GlideClientConfiguration, GlideClusterClientConfiguration};
18use crate::executor::CustomCommand;
19use crate::pipeline_options::PipelineOptions;
20use crate::routes::Route;
21use crate::write::ToValkeyArgs;
22use crate::{ValkeyResult, ValkeyValue};
23use std::future::Future;
24use std::sync::OnceLock;
25use tokio::runtime::{Builder, Runtime};
26
27mod pipeline;
28pub use pipeline::PipelineExt;
29
30fn runtime() -> &'static Runtime {
31    static RUNTIME: OnceLock<Runtime> = OnceLock::new();
32    RUNTIME.get_or_init(|| {
33        Builder::new_multi_thread()
34            .enable_all()
35            .thread_name("glide-sync")
36            .build()
37            .expect("failed to build the shared GLIDE sync runtime")
38    })
39}
40
41/// Block on an arbitrary future using the shared runtime.
42///
43/// Must not be called from within an async context (a running Tokio runtime) –
44/// doing so panics with "cannot block the current thread from within a runtime".
45pub fn block_on<F: Future>(future: F) -> F::Output {
46    runtime().block_on(future)
47}
48
49/// A blocking client for a **standalone** deployment.
50#[derive(Clone)]
51pub struct SyncGlideClient {
52    inner: GlideClient,
53}
54
55impl SyncGlideClient {
56    /// Connect using the given standalone configuration (blocking).
57    pub fn connect(config: GlideClientConfiguration) -> ValkeyResult<Self> {
58        let inner = runtime().block_on(GlideClient::connect(config))?;
59        Ok(SyncGlideClient { inner })
60    }
61
62    /// The underlying async client.
63    pub fn async_client(&self) -> &GlideClient {
64        &self.inner
65    }
66
67    /// Run an arbitrary async operation against the client, blocking until it
68    /// completes. This unlocks the *entire* async command surface from sync code:
69    ///
70    /// ```rust,no_run
71    /// # use glide::sync::SyncGlideClient;
72    /// # use glide::{AsyncCommands, GlideClientConfiguration};
73    /// # fn demo(client: SyncGlideClient) -> glide::ValkeyResult<()> {
74    /// let value: Option<String> = client.run(|c| async move { c.get("key").await })?;
75    /// # let _ = value; Ok(()) }
76    /// ```
77    pub fn run<F, Fut, T>(&self, f: F) -> T
78    where
79        F: FnOnce(GlideClient) -> Fut,
80        Fut: Future<Output = T>,
81    {
82        runtime().block_on(f(self.inner.clone()))
83    }
84
85    /// Update the connection password (blocking). See
86    /// [`GlideClient::update_connection_password`].
87    pub fn update_connection_password(
88        &self,
89        password: Option<String>,
90        immediate_auth: bool,
91    ) -> ValkeyResult<()> {
92        runtime().block_on(
93            self.inner
94                .update_connection_password(password, immediate_auth),
95        )
96    }
97
98    /// Run an arbitrary command (blocking escape hatch).
99    pub fn custom_command<A: ToValkeyArgs + Sync>(&self, args: &[A]) -> ValkeyResult<ValkeyValue> {
100        runtime().block_on(self.inner.custom_command(args))
101    }
102
103    /// Execute a [`crate::Pipeline`] with GLIDE execution options
104    /// (blocking). See [`crate::GlideClient::exec`]; for plain
105    /// typed execution prefer [`PipelineExt::query`].
106    pub fn exec(
107        &self,
108        pipeline: &crate::pipeline::Pipeline,
109        raise_on_error: bool,
110        options: &PipelineOptions,
111    ) -> ValkeyResult<Vec<ValkeyValue>> {
112        runtime().block_on(self.inner.exec(pipeline, raise_on_error, options))
113    }
114
115    /// Blocking `PING`.
116    pub fn ping(&self) -> ValkeyResult<String> {
117        runtime().block_on(AsyncTypedCommands::ping(&self.inner))
118    }
119}
120
121/// A blocking client for a **cluster** deployment.
122#[derive(Clone)]
123pub struct SyncGlideClusterClient {
124    inner: GlideClusterClient,
125}
126
127impl SyncGlideClusterClient {
128    /// Connect using the given cluster configuration (blocking).
129    pub fn connect(config: GlideClusterClientConfiguration) -> ValkeyResult<Self> {
130        let inner = runtime().block_on(GlideClusterClient::connect(config))?;
131        Ok(SyncGlideClusterClient { inner })
132    }
133
134    /// The underlying async client.
135    pub fn async_client(&self) -> &GlideClusterClient {
136        &self.inner
137    }
138
139    /// Run an arbitrary async operation against the client (blocking).
140    pub fn run<F, Fut, T>(&self, f: F) -> T
141    where
142        F: FnOnce(GlideClusterClient) -> Fut,
143        Fut: Future<Output = T>,
144    {
145        runtime().block_on(f(self.inner.clone()))
146    }
147
148    /// Run an arbitrary command (blocking escape hatch).
149    pub fn custom_command<A: ToValkeyArgs + Sync>(&self, args: &[A]) -> ValkeyResult<ValkeyValue> {
150        runtime().block_on(self.inner.custom_command(args))
151    }
152
153    /// Run an arbitrary command with an explicit route (blocking).
154    pub fn custom_command_with_route<A: ToValkeyArgs + Sync>(
155        &self,
156        args: &[A],
157        route: Route,
158    ) -> ValkeyResult<ValkeyValue> {
159        runtime().block_on(self.inner.custom_command_with_route(args, route))
160    }
161
162    /// Update the connection password (blocking). See
163    /// [`GlideClusterClient::update_connection_password`].
164    pub fn update_connection_password(
165        &self,
166        password: Option<String>,
167        immediate_auth: bool,
168    ) -> ValkeyResult<()> {
169        runtime().block_on(
170            self.inner
171                .update_connection_password(password, immediate_auth),
172        )
173    }
174
175    /// Execute a [`crate::pipeline::Pipeline`] with GLIDE execution options,
176    /// optionally routed (blocking). See
177    /// [`crate::GlideClusterClient::exec`].
178    pub fn exec(
179        &self,
180        pipeline: &crate::pipeline::Pipeline,
181        raise_on_error: bool,
182        route: Option<crate::Route>,
183        options: &PipelineOptions,
184    ) -> ValkeyResult<Vec<ValkeyValue>> {
185        runtime().block_on(self.inner.exec(pipeline, raise_on_error, route, options))
186    }
187
188    /// Blocking `PING`.
189    pub fn ping(&self) -> ValkeyResult<String> {
190        runtime().block_on(AsyncTypedCommands::ping(&self.inner))
191    }
192}
193
194// ---- Command dispatch -------------------------------------------------------
195
196macro_rules! impl_sync_command_dispatch {
197    ($ty:ty) => {
198        #[::sealed::sealed]
199        impl crate::commands::core::SyncCommandDispatch for $ty {
200            fn glide_dispatch_command(&self, cmd: crate::cmd::Cmd) -> ValkeyResult<ValkeyValue> {
201                runtime().block_on(
202                    crate::commands::core::CommandDispatch::glide_dispatch_command(
203                        &self.inner,
204                        cmd,
205                    ),
206                )
207            }
208        }
209    };
210}
211
212impl_sync_command_dispatch!(SyncGlideClient);
213impl_sync_command_dispatch!(SyncGlideClusterClient);
214
215// ---- Script dispatch --------------------------------------------------------
216
217macro_rules! impl_sync_script_invoke {
218    ($ty:ty) => {
219        #[::sealed::sealed]
220        impl crate::script::ScriptInvokeSync for $ty {
221            fn glide_invoke_script(
222                &self,
223                hash: &str,
224                keys: &[Vec<u8>],
225                args: &[Vec<u8>],
226            ) -> ValkeyResult<ValkeyValue> {
227                runtime().block_on(crate::script::ScriptInvoke::glide_invoke_script(
228                    &self.inner,
229                    hash,
230                    keys,
231                    args,
232                ))
233            }
234        }
235    };
236}
237
238impl_sync_script_invoke!(SyncGlideClient);
239impl_sync_script_invoke!(SyncGlideClusterClient);
240
241// ---- Pipeline dispatch ------------------------------------------------------
242
243macro_rules! impl_sync_pipeline_dispatch {
244    ($ty:ty) => {
245        #[::sealed::sealed]
246        impl pipeline::SyncPipelineDispatch for $ty {
247            fn glide_dispatch_pipeline<T: crate::value::FromValkeyValue + Send>(
248                &self,
249                pipeline: &crate::pipeline::Pipeline,
250            ) -> ValkeyResult<T> {
251                let async_conn = self.inner.clone();
252                runtime().block_on(crate::client::PipelineExt::query_async(
253                    pipeline,
254                    &async_conn,
255                ))
256            }
257        }
258    };
259}
260
261impl_sync_pipeline_dispatch!(SyncGlideClient);
262impl_sync_pipeline_dispatch!(SyncGlideClusterClient);