1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! Internal tokio runtime backing the sync (blocking) API.
//!
//! The runtime is created lazily on first use and intentionally lives for the
//! process lifetime — there is no shutdown path (see [`RUNTIME`]).
use Future;
use OnceLock;
use ;
use crateHonchoError;
/// Canonical message returned when the blocking API is driven from inside an
/// async runtime. Shared with other call sites that surface the same diagnostic.
pub const BLOCKING_IN_ASYNC_CTX_MSG: &str =
"blocking API cannot be used from inside an async runtime; use the async Honcho client instead";
/// Lazily-created global multi-thread tokio runtime.
///
/// Stored as an [`std::io::Result`] so that a failure to build the runtime
/// (e.g. OS thread/fd limits exhausted on the very first SDK call) is reported
/// as a [`HonchoError::Configuration`] instead of panicking the host process.
///
/// The runtime is intentionally never shut down: it is an idempotent singleton
/// owned by the process, and tearing it down on `Drop` would break any in-flight
/// blocking call still holding a borrowed [`Handle`].
static RUNTIME: = new;
/// Returns worker count: `min(available_parallelism, 8)`, default 4 when the
/// platform cannot report parallelism.
///
/// The 8-worker cap is currently hardcoded; a public builder knob to override
/// it is out of scope for this PR (signature change deferred to a follow-up).
/// Obtain a [`Handle`] to the global runtime, creating it on first call.
///
/// # Contract
///
/// The returned handle **must only be driven from a plain (non-async) thread**.
/// It is the seam used by
/// [`upload_file_streamed`](crate::blocking::Session::upload_file_streamed) to
/// bridge a synchronous reader onto an async multipart stream. Calling
/// `handle.block_on(...)` from inside another async runtime bypasses the
/// [`block_on`] guard and triggers tokio's own "cannot start a runtime from
/// within a runtime" panic.
///
/// # Errors
///
/// Returns [`HonchoError::Configuration`] if the global runtime could not be
/// built (e.g. resource limits).
pub
/// Run `future` to completion on the global runtime.
///
/// This is the single funnel every sync (blocking) SDK method goes through.
///
/// # Guard
///
/// Refuses to run when a tokio runtime is already bound to the current thread
/// (`Handle::try_current()` succeeds): nesting runtimes panics inside tokio.
/// The rejection is intentionally conservative — it also trips for legitimate
/// `spawn_blocking` / `block_in_place` contexts where `Runtime::block_on` would
/// in fact be legal, and it cannot detect non-tokio executors. Broadening the
/// detection is risky and out of scope for this PR; the actionable error
/// message ([`BLOCKING_IN_ASYNC_CTX_MSG`]) points users at the async client.
///
/// # Errors
///
/// Returns [`HonchoError::Configuration`] (with [`BLOCKING_IN_ASYNC_CTX_MSG`])
/// when called from inside an async runtime, or if the global runtime could not
/// be built.
pub