1use std::future::Future;
2use std::sync::Arc;
3
4use crate::client::HFClient;
5use crate::error::{HFError, HFResult};
6use crate::repository::{HFRepository, RepoType, RepoTypeDataset, RepoTypeKernel, RepoTypeModel, RepoTypeSpace};
7
8pub(crate) struct RuntimeThread {
20 handle: tokio::runtime::Handle,
21 _shutdown: futures::channel::oneshot::Sender<()>,
22}
23
24impl RuntimeThread {
25 fn spawn() -> HFResult<Arc<Self>> {
27 let (handle_tx, handle_rx) = std::sync::mpsc::sync_channel(1);
28 let (shutdown_tx, shutdown_rx) = futures::channel::oneshot::channel::<()>();
29 std::thread::Builder::new()
30 .name("hf-hub-blocking-runtime".to_string())
31 .spawn(move || {
32 let runtime = match tokio::runtime::Builder::new_current_thread().enable_all().build() {
33 Ok(runtime) => runtime,
34 Err(e) => {
35 let _ = handle_tx.send(Err(e));
36 return;
37 },
38 };
39 let _ = handle_tx.send(Ok(runtime.handle().clone()));
40 runtime.block_on(async {
41 let _ = shutdown_rx.await;
42 });
43 })
44 .map_err(|e| HFError::Other(format!("Failed to spawn tokio runtime thread: {e}")))?;
45 handle_rx
46 .recv()
47 .map_err(|_| HFError::Other("tokio runtime thread exited before initializing".to_string()))?
48 .map_err(|e| HFError::Other(format!("Failed to create tokio runtime: {e}")))
49 .map(|handle| {
50 Arc::new(Self {
51 handle,
52 _shutdown: shutdown_tx,
53 })
54 })
55 }
56
57 pub(crate) fn block_on<F>(&self, future: F) -> F::Output
69 where
70 F: Future + Send,
71 F::Output: Send,
72 {
73 std::thread::scope(|scope| {
74 scope
75 .spawn(|| self.handle.block_on(future))
76 .join()
77 .unwrap_or_else(|panic| std::panic::resume_unwind(panic))
78 })
79 }
80}
81
82#[cfg_attr(docsrs, doc(cfg(feature = "blocking")))]
98#[derive(Clone)]
99pub struct HFClientSync {
100 pub(crate) inner: HFClient,
101 pub(crate) runtime: Arc<RuntimeThread>,
102}
103
104#[cfg_attr(docsrs, doc(cfg(feature = "blocking")))]
110pub struct HFRepositorySync<T: RepoType> {
111 pub(crate) inner: Arc<HFRepository<T>>,
112 pub(crate) runtime: Arc<RuntimeThread>,
113}
114
115impl<T: RepoType> Clone for HFRepositorySync<T> {
116 fn clone(&self) -> Self {
117 Self {
118 inner: Arc::clone(&self.inner),
119 runtime: Arc::clone(&self.runtime),
120 }
121 }
122}
123
124#[cfg_attr(docsrs, doc(cfg(feature = "blocking")))]
131#[derive(Clone)]
132pub struct HFBucketSync {
133 pub(crate) inner: Arc<crate::buckets::HFBucket>,
134 pub(crate) runtime: Arc<RuntimeThread>,
135}
136
137impl std::fmt::Debug for HFClientSync {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 f.debug_struct("HFClientSync").finish()
140 }
141}
142
143impl<T: RepoType> std::fmt::Debug for HFRepositorySync<T> {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 f.debug_struct("HFRepositorySync").field("inner", &self.inner).finish()
146 }
147}
148
149impl std::fmt::Debug for HFBucketSync {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 f.debug_struct("HFBucketSync").field("inner", &self.inner).finish()
152 }
153}
154
155impl HFClientSync {
156 pub fn new() -> HFResult<Self> {
164 Ok(Self {
165 inner: HFClient::new()?,
166 runtime: RuntimeThread::spawn()?,
167 })
168 }
169
170 pub fn from_inner(inner: HFClient) -> HFResult<Self> {
176 Ok(Self {
177 inner,
178 runtime: RuntimeThread::spawn()?,
179 })
180 }
181
182 pub fn repository<T: RepoType>(
186 &self,
187 repo_type: T,
188 owner: impl Into<String>,
189 name: impl Into<String>,
190 ) -> HFRepositorySync<T> {
191 HFRepositorySync::new(self.clone(), repo_type, owner, name)
192 }
193
194 pub fn model(&self, owner: impl Into<String>, name: impl Into<String>) -> HFRepositorySync<RepoTypeModel> {
198 self.repository(RepoTypeModel, owner, name)
199 }
200
201 pub fn dataset(&self, owner: impl Into<String>, name: impl Into<String>) -> HFRepositorySync<RepoTypeDataset> {
205 self.repository(RepoTypeDataset, owner, name)
206 }
207
208 pub fn space(&self, owner: impl Into<String>, name: impl Into<String>) -> HFRepositorySync<RepoTypeSpace> {
212 self.repository(RepoTypeSpace, owner, name)
213 }
214
215 pub fn kernel(&self, owner: impl Into<String>, name: impl Into<String>) -> HFRepositorySync<RepoTypeKernel> {
219 self.repository(RepoTypeKernel, owner, name)
220 }
221
222 pub fn bucket(&self, owner: impl Into<String>, name: impl Into<String>) -> HFBucketSync {
226 HFBucketSync::new(self.clone(), owner, name)
227 }
228}
229
230impl<T: RepoType> HFRepositorySync<T> {
231 pub fn new(client: HFClientSync, repo_type: T, owner: impl Into<String>, name: impl Into<String>) -> Self {
235 Self {
236 inner: Arc::new(HFRepository::new(client.inner.clone(), repo_type, owner, name)),
237 runtime: client.runtime.clone(),
238 }
239 }
240
241 pub fn owner(&self) -> &str {
243 self.inner.owner()
244 }
245
246 pub fn name(&self) -> &str {
248 self.inner.name()
249 }
250
251 pub fn repo_path(&self) -> String {
255 self.inner.repo_path()
256 }
257
258 pub fn repo_type(&self) -> &T {
262 self.inner.repo_type()
263 }
264}
265
266impl HFBucketSync {
267 pub fn new(client: HFClientSync, owner: impl Into<String>, name: impl Into<String>) -> Self {
271 Self {
272 inner: Arc::new(crate::buckets::HFBucket::new(client.inner.clone(), owner, name)),
273 runtime: client.runtime.clone(),
274 }
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn test_hfapisync_creation() {
284 let client = HFClientSync::new();
285 assert!(client.is_ok());
286 }
287
288 #[test]
289 fn test_hfapisync_from_api() {
290 let async_client = HFClient::builder().build().unwrap();
291 let client = HFClientSync::from_inner(async_client);
292 assert!(client.is_ok());
293 }
294
295 #[test]
296 fn test_sync_repo_constructors() {
297 let client = HFClientSync::from_inner(HFClient::builder().build().unwrap()).unwrap();
298 let repo = client.model("openai-community", "gpt2");
299 let space = client.space("huggingface", "transformers-benchmarks");
300
301 assert_eq!(repo.owner(), "openai-community");
302 assert_eq!(repo.name(), "gpt2");
303 assert_eq!(repo.repo_type().singular(), "model");
304 assert_eq!(space.repo_type().singular(), "space");
305 }
306
307 fn unreachable_endpoint() -> String {
309 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
310 let port = listener.local_addr().unwrap().port();
311 drop(listener);
312 format!("http://127.0.0.1:{port}")
313 }
314
315 fn client_with_unreachable_endpoint() -> HFClientSync {
316 let inner = HFClient::builder()
317 .endpoint(unreachable_endpoint())
318 .retry_max_attempts(0)
319 .build()
320 .unwrap();
321 HFClientSync::from_inner(inner).unwrap()
322 }
323
324 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
328 async fn sync_call_inside_multi_thread_runtime_does_not_panic() {
329 let client = client_with_unreachable_endpoint();
330 let result = client.model("openai-community", "gpt2").info().send();
331 assert!(result.is_err());
332 }
333
334 #[tokio::test]
335 async fn sync_call_inside_current_thread_runtime_does_not_panic() {
336 let client = client_with_unreachable_endpoint();
337 let result = client.model("openai-community", "gpt2").info().send();
338 assert!(result.is_err());
339 }
340
341 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
344 async fn construct_and_drop_inside_async_context_does_not_panic() {
345 let client = HFClientSync::from_inner(HFClient::builder().build().unwrap()).unwrap();
346 drop(client);
347 }
348
349 #[test]
350 fn clones_share_runtime_thread_and_survive_staggered_drops() {
351 let client = client_with_unreachable_endpoint();
352 let clone = client.clone();
353 let repo = client.model("openai-community", "gpt2");
354 assert!(Arc::ptr_eq(&client.runtime, &clone.runtime));
355 assert!(Arc::ptr_eq(&client.runtime, &repo.runtime));
356
357 drop(client);
358 drop(clone);
359 let result = repo.exists().send();
360 assert!(result.is_err());
361 }
362
363 #[test]
364 fn block_on_resumes_task_panics_on_the_caller() {
365 let runtime = RuntimeThread::spawn().unwrap();
366 let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
367 runtime.block_on(async {
368 panic!("boom");
369 })
370 }));
371 let panic = caught.unwrap_err();
372 assert_eq!(panic.downcast_ref::<&str>(), Some(&"boom"));
373 }
374
375 #[test]
378 fn block_on_accepts_borrowing_futures() {
379 let runtime = RuntimeThread::spawn().unwrap();
380 let data = String::from("borrowed");
381 let borrowed = &data;
382 let len = runtime.block_on(async move { borrowed.len() });
383 assert_eq!(len, data.len());
384 }
385}