wreq 1.2.12 → 1.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/src/arch.rs CHANGED
@@ -8,35 +8,63 @@
8
8
 
9
9
  use std::mem::ManuallyDrop;
10
10
 
11
- /// Native state that belongs to the process where the extension was loaded.
11
+ use magnus::Ruby;
12
+
13
+ #[cfg(unix)]
14
+ use crate::error::fork_error;
15
+
16
+ /// Native state that belongs to the process where it was created.
12
17
  ///
13
18
  /// A forked child must not destroy inherited clients, channels, or response
14
19
  /// bodies because their synchronization state may belong to threads that no
15
20
  /// longer exist. The child intentionally leaks the value and lets the operating
16
21
  /// system reclaim it when the process exits.
17
22
  ///
18
- /// This wrapper only controls destruction. Call [`crate::rt::ensure_current`]
19
- /// before using process-bound state stored inside it.
20
- #[derive(Clone)]
21
- pub(crate) struct ProcessLocal<T>(ManuallyDrop<T>);
23
+ /// `Send` and `Sync` only describe access between threads in one process. They
24
+ /// do not make a runtime, lock, channel, or connection pool safe after `fork`.
25
+ ///
26
+ /// [`ProcessLocal::get`] is the only access path and checks the object's own
27
+ /// process generation before exposing its value.
28
+ pub(crate) struct ProcessLocal<T> {
29
+ value: ManuallyDrop<T>,
30
+ #[cfg(unix)]
31
+ owner: unix::ProcessToken,
32
+ }
22
33
 
23
34
  impl<T> ProcessLocal<T> {
24
35
  /// Wrap native state created by the current process.
25
36
  pub(crate) fn new(value: T) -> Self {
26
- Self(ManuallyDrop::new(value))
37
+ Self {
38
+ value: ManuallyDrop::new(value),
39
+ #[cfg(unix)]
40
+ owner: unix::ProcessToken::current(),
41
+ }
27
42
  }
28
- }
29
43
 
30
- impl<T> AsRef<T> for ProcessLocal<T> {
31
- fn as_ref(&self) -> &T {
32
- &self.0
44
+ /// Borrow native state only from the process that created it.
45
+ ///
46
+ /// # Errors
47
+ ///
48
+ /// Returns `Wreq::ForkError` when the value was inherited from a parent
49
+ /// process.
50
+ #[inline]
51
+ pub(crate) fn get(&self, ruby: &Ruby) -> Result<&T, magnus::Error> {
52
+ #[cfg(unix)]
53
+ if let Some((owner_pid, current_pid)) = self.owner.forked_process_ids() {
54
+ return Err(fork_error(ruby, owner_pid, current_pid));
55
+ }
56
+
57
+ #[cfg(not(unix))]
58
+ let _ = ruby;
59
+
60
+ Ok(&self.value)
33
61
  }
34
62
  }
35
63
 
36
64
  impl<T> Drop for ProcessLocal<T> {
37
65
  fn drop(&mut self) {
38
66
  #[cfg(unix)]
39
- if forked_process_ids().is_some() {
67
+ if self.owner.forked_process_ids().is_some() {
40
68
  return;
41
69
  }
42
70
 
@@ -44,7 +72,7 @@ impl<T> Drop for ProcessLocal<T> {
44
72
  // prevents an automatic second drop, and this wrapper's `Drop`
45
73
  // implementation runs at most once.
46
74
  unsafe {
47
- ManuallyDrop::drop(&mut self.0);
75
+ ManuallyDrop::drop(&mut self.value);
48
76
  }
49
77
  }
50
78
  }
@@ -74,54 +102,72 @@ pub(crate) const SUPPORTS_INTERFACE: bool = cfg!(any(
74
102
  mod unix {
75
103
  use std::{io, process, sync::OnceLock};
76
104
 
77
- /// Process state captured when the extension initializes.
105
+ /// Identity of the process generation that created native state.
78
106
  ///
79
- /// The atfork guard uses a POSIX child handler to advance an atomic fork
80
- /// generation without running Ruby code.
107
+ /// Forkguard's child callback only advances an atomic generation counter.
108
+ /// The PID is retained for diagnostics and as a fallback if registering
109
+ /// the callback fails.
81
110
  /// https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_atfork.html
82
- struct ForkGuard {
83
- detector: forkguard::Guard,
111
+ pub(super) struct ProcessToken {
112
+ detector: Option<forkguard::Guard>,
84
113
  owner_pid: u32,
85
114
  }
86
115
 
87
- impl ForkGuard {
88
- /// Create a guard and register fork detection with the process.
89
- fn new() -> io::Result<Self> {
116
+ impl ProcessToken {
117
+ /// Capture the current process and fork generation.
118
+ pub(super) fn current() -> Self {
119
+ Self::try_current().unwrap_or_else(|_| Self {
120
+ detector: None,
121
+ owner_pid: process::id(),
122
+ })
123
+ }
124
+
125
+ /// Capture the current process after registering fork detection.
126
+ fn try_current() -> io::Result<Self> {
90
127
  forkguard::Guard::try_new()
91
128
  .map(|detector| Self {
92
- detector,
129
+ detector: Some(detector),
93
130
  owner_pid: process::id(),
94
131
  })
95
132
  .map_err(|error| io::Error::from_raw_os_error(error.code().get()))
96
133
  }
97
134
 
98
- /// Return process IDs when this guard was inherited through a fork.
99
- fn forked_process_ids(&self) -> Option<(u32, u32)> {
100
- // Keep the stored generation unchanged so every runtime access in
101
- // the child remains rejected. Cloning the detector copies one usize.
102
- let mut detector = self.detector.clone();
103
- detector
104
- .detected_fork()
105
- .then(|| (self.owner_pid, process::id()))
135
+ /// Return process IDs when this token was inherited through a fork.
136
+ pub(super) fn forked_process_ids(&self) -> Option<(u32, u32)> {
137
+ if let Some(detector) = &self.detector {
138
+ // Keep the stored generation unchanged so repeated accesses
139
+ // continue to reject the same inherited object. Cloning the
140
+ // detector copies one usize.
141
+ return detector
142
+ .clone()
143
+ .detected_fork()
144
+ .then(|| (self.owner_pid, process::id()));
145
+ }
146
+
147
+ let current_pid = process::id();
148
+ (self.owner_pid != current_pid).then_some((self.owner_pid, current_pid))
106
149
  }
107
150
  }
108
151
 
109
- static FORK_GUARD: OnceLock<ForkGuard> = OnceLock::new();
152
+ /// Runtime owner captured when Tokio first initializes.
153
+ static RUNTIME_OWNER: OnceLock<ProcessToken> = OnceLock::new();
110
154
 
111
- /// Register process fork tracking before the extension exposes its API.
155
+ /// Register process fork tracking before the Tokio runtime is initialized.
112
156
  pub(crate) fn initialize_fork_tracking() -> io::Result<()> {
113
- if FORK_GUARD.get().is_some() {
157
+ if RUNTIME_OWNER.get().is_some() {
114
158
  return Ok(());
115
159
  }
116
160
 
117
- let guard = ForkGuard::new()?;
118
- let _ = FORK_GUARD.set(guard);
161
+ let owner = ProcessToken::try_current()?;
162
+ let _ = RUNTIME_OWNER.set(owner);
119
163
  Ok(())
120
164
  }
121
165
 
122
- /// Return process IDs only when this process inherited the extension.
166
+ /// Return process IDs when this process inherited an initialized runtime.
123
167
  pub(crate) fn forked_process_ids() -> Option<(u32, u32)> {
124
- FORK_GUARD.get().and_then(ForkGuard::forked_process_ids)
168
+ RUNTIME_OWNER
169
+ .get()
170
+ .and_then(ProcessToken::forked_process_ids)
125
171
  }
126
172
  }
127
173
 
@@ -175,7 +221,7 @@ mod tests {
175
221
 
176
222
  {
177
223
  let value = ProcessLocal::new(DropCounter(&drops));
178
- assert_eq!(value.as_ref().0.get(), 0);
224
+ assert_eq!(value.value.0.get(), 0);
179
225
  }
180
226
 
181
227
  assert_eq!(drops.get(), 1);
@@ -65,17 +65,14 @@ impl BodyReceiver {
65
65
 
66
66
  /// Read the next body chunk, converting stream errors into Ruby errors.
67
67
  pub fn next(&self, ruby: &Ruby) -> Result<Option<Bytes>, Error> {
68
- rt::try_block_on(
69
- ruby,
70
- async {
71
- match self.0.lock().await.as_mut().next().await {
72
- Some(Ok(data)) => Ok(Some(data)),
73
- Some(Err(err)) => Err(err),
74
- None => Ok(None),
75
- }
76
- },
77
- wreq_error,
78
- )
68
+ rt::block_on(ruby, async {
69
+ match self.0.lock().await.as_mut().next().await {
70
+ Some(Ok(data)) => Ok(Some(data)),
71
+ Some(Err(err)) => Err(err),
72
+ None => Ok(None),
73
+ }
74
+ })?
75
+ .map_err(|err| wreq_error(ruby, err))
79
76
  }
80
77
  }
81
78
 
@@ -90,10 +87,8 @@ impl BodySender {
90
87
  /// # Errors
91
88
  ///
92
89
  /// Returns `TypeError` for a non-Integer capacity and `ArgumentError` for
93
- /// an invalid range or argument count. Returns `Wreq::ForkError` before
94
- /// creating a channel in a child that inherited the extension.
90
+ /// an invalid range or argument count.
95
91
  pub fn new(ruby: &Ruby, args: &[Value]) -> Result<Self, Error> {
96
- rt::ensure_current(ruby)?;
97
92
  let capacity = parse_capacity(ruby, args)?;
98
93
 
99
94
  // Create the Tokio channel without allowing an unwind to cross the Ruby FFI boundary.
@@ -121,8 +116,6 @@ impl BodySender {
121
116
  /// wait raises `Wreq::InterruptError`. Returns `Wreq::ForkError` before
122
117
  /// reading an inherited channel.
123
118
  pub fn push(ruby: &Ruby, rb_self: &Self, data: RString) -> Result<(), Error> {
124
- rt::ensure_current(ruby)?;
125
-
126
119
  // Clone during the shared borrow, then release it before waiting
127
120
  // for capacity. Request attachment needs a mutable borrow.
128
121
  let tx = match &rb_self.read_inner(ruby)?.tx {
@@ -130,7 +123,8 @@ impl BodySender {
130
123
  _ => return Err(closed_body_sender_error(ruby)),
131
124
  };
132
125
 
133
- rt::try_block_on(ruby, tx.send(data.to_bytes()), body_sender_send_error)
126
+ rt::block_on(ruby, tx.send(data.to_bytes()))?
127
+ .map_err(|err| body_sender_send_error(ruby, err))
134
128
  }
135
129
 
136
130
  /// Close the producing side while retaining the receiver and queued chunks.
@@ -142,7 +136,6 @@ impl BodySender {
142
136
  /// Returns `Wreq::ForkError` before reading an inherited channel, or
143
137
  /// `Wreq::BodyError` if the internal state is already borrowed.
144
138
  pub fn close(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
145
- rt::ensure_current(ruby)?;
146
139
  let mut inner = rb_self.write_inner(ruby)?;
147
140
  inner.tx.take();
148
141
  Ok(())
@@ -155,22 +148,21 @@ impl BodySender {
155
148
  /// Returns `Wreq::ForkError` before reading an inherited channel, or
156
149
  /// `Wreq::BodyError` if the internal state is already borrowed.
157
150
  pub fn is_closed(ruby: &Ruby, rb_self: &Self) -> Result<bool, Error> {
158
- rt::ensure_current(ruby)?;
159
151
  rb_self.read_inner(ruby).map(|r| r.is_closed())
160
152
  }
161
153
 
162
- /// Borrow the channel state without panicking on accidental re-entry.
154
+ /// Borrow channel state only in the process that created this sender.
163
155
  fn read_inner(&self, ruby: &Ruby) -> Result<Ref<'_, InnerBodySender>, Error> {
164
156
  self.0
165
- .as_ref()
157
+ .get(ruby)?
166
158
  .try_borrow()
167
159
  .map_err(|err| body_sender_borrow_error(ruby, err))
168
160
  }
169
161
 
170
- /// Mutably borrow the channel state without panicking on accidental re-entry.
162
+ /// Mutably borrow channel state only in the process that created this sender.
171
163
  fn write_inner(&self, ruby: &Ruby) -> Result<RefMut<'_, InnerBodySender>, Error> {
172
164
  self.0
173
- .as_ref()
165
+ .get(ruby)?
174
166
  .try_borrow_mut()
175
167
  .map_err(|err| body_sender_borrow_mut_error(ruby, err))
176
168
  }
@@ -182,7 +174,6 @@ impl BodySender {
182
174
  /// Returns `Wreq::MemoryError` if the receiver was already consumed, or
183
175
  /// `Wreq::BodyError` if Ruby re-enters while the state is borrowed.
184
176
  pub(super) fn take_receiver(&self, ruby: &Ruby) -> Result<ReceiverStream<Bytes>, Error> {
185
- rt::ensure_current(ruby)?;
186
177
  self.write_inner(ruby)?
187
178
  .rx
188
179
  .take()
data/src/client/req.rs CHANGED
@@ -184,131 +184,128 @@ pub fn execute_request<U: AsRef<str>>(
184
184
  url: U,
185
185
  mut request: Request,
186
186
  ) -> Result<Response, magnus::Error> {
187
- rt::try_block_on(
188
- ruby,
189
- async move {
190
- let mut builder = client.request(method.into_ffi(), url.as_ref());
191
-
192
- // Emulation options.
193
- apply_option!(set_if_some_inner, builder, request.emulation, emulation);
194
-
195
- // Version options.
196
- apply_option!(
197
- set_if_some_map,
198
- builder,
199
- request.version,
200
- version,
201
- Version::into_ffi
202
- );
203
-
204
- // Timeout options.
205
- apply_option!(
206
- set_if_some_map,
207
- builder,
208
- request.timeout,
209
- timeout,
210
- Duration::from_secs
211
- );
212
- apply_option!(
213
- set_if_some_map,
214
- builder,
215
- request.read_timeout,
216
- read_timeout,
217
- Duration::from_secs
218
- );
219
-
220
- // Network options.
221
- apply_option!(set_if_some, builder, request.proxy, proxy);
222
- apply_option!(set_if_some, builder, request.local_address, local_address);
223
- #[cfg(any(
224
- target_os = "android",
225
- target_os = "fuchsia",
226
- target_os = "illumos",
227
- target_os = "ios",
228
- target_os = "linux",
229
- target_os = "macos",
230
- target_os = "solaris",
231
- target_os = "tvos",
232
- target_os = "visionos",
233
- target_os = "watchos",
234
- ))]
235
- apply_option!(set_if_some, builder, request.interface, interface);
236
-
237
- // Headers options.
238
- apply_option!(set_if_some_into_inner, builder, request.headers, headers);
239
- apply_option!(
240
- set_if_some_inner,
241
- builder,
242
- request.orig_headers,
243
- orig_headers
244
- );
245
- apply_option!(
246
- set_if_some,
247
- builder,
248
- request.default_headers,
249
- default_headers
250
- );
251
-
252
- // Cookies options.
253
- if let Some(cookies) = request.cookies.take() {
254
- for cookie in cookies.0 {
255
- builder = builder.header(header::COOKIE, cookie);
256
- }
257
- }
187
+ rt::block_on(ruby, async move {
188
+ let mut builder = client.request(method.into_ffi(), url.as_ref());
189
+
190
+ // Emulation options.
191
+ apply_option!(set_if_some_inner, builder, request.emulation, emulation);
192
+
193
+ // Version options.
194
+ apply_option!(
195
+ set_if_some_map,
196
+ builder,
197
+ request.version,
198
+ version,
199
+ Version::into_ffi
200
+ );
201
+
202
+ // Timeout options.
203
+ apply_option!(
204
+ set_if_some_map,
205
+ builder,
206
+ request.timeout,
207
+ timeout,
208
+ Duration::from_secs
209
+ );
210
+ apply_option!(
211
+ set_if_some_map,
212
+ builder,
213
+ request.read_timeout,
214
+ read_timeout,
215
+ Duration::from_secs
216
+ );
258
217
 
259
- // Authentication options.
260
- apply_option!(
261
- set_if_some_map_ref,
262
- builder,
263
- request.auth,
264
- auth,
265
- AsRef::<str>::as_ref
266
- );
267
- apply_option!(set_if_some, builder, request.bearer_auth, bearer_auth);
268
- if let Some(basic_auth) = request.basic_auth.take() {
269
- builder = builder.basic_auth(basic_auth.0, basic_auth.1);
218
+ // Network options.
219
+ apply_option!(set_if_some, builder, request.proxy, proxy);
220
+ apply_option!(set_if_some, builder, request.local_address, local_address);
221
+ #[cfg(any(
222
+ target_os = "android",
223
+ target_os = "fuchsia",
224
+ target_os = "illumos",
225
+ target_os = "ios",
226
+ target_os = "linux",
227
+ target_os = "macos",
228
+ target_os = "solaris",
229
+ target_os = "tvos",
230
+ target_os = "visionos",
231
+ target_os = "watchos",
232
+ ))]
233
+ apply_option!(set_if_some, builder, request.interface, interface);
234
+
235
+ // Headers options.
236
+ apply_option!(set_if_some_into_inner, builder, request.headers, headers);
237
+ apply_option!(
238
+ set_if_some_inner,
239
+ builder,
240
+ request.orig_headers,
241
+ orig_headers
242
+ );
243
+ apply_option!(
244
+ set_if_some,
245
+ builder,
246
+ request.default_headers,
247
+ default_headers
248
+ );
249
+
250
+ // Cookies options.
251
+ if let Some(cookies) = request.cookies.take() {
252
+ for cookie in cookies.0 {
253
+ builder = builder.header(header::COOKIE, cookie);
270
254
  }
255
+ }
271
256
 
272
- // Allow redirects options.
273
- match request.allow_redirects {
274
- Some(false) => {
275
- builder = builder.redirect(wreq::redirect::Policy::none());
276
- }
277
- Some(true) => {
278
- builder = builder.redirect(
279
- request
280
- .max_redirects
281
- .take()
282
- .map(wreq::redirect::Policy::limited)
283
- .unwrap_or_default(),
284
- );
285
- }
286
- None => {}
287
- };
288
-
289
- // Compression options.
290
- apply_option!(set_if_some, builder, request.gzip, gzip);
291
- apply_option!(set_if_some, builder, request.brotli, brotli);
292
- apply_option!(set_if_some, builder, request.deflate, deflate);
293
- apply_option!(set_if_some, builder, request.zstd, zstd);
294
-
295
- // Query options.
296
- apply_option!(set_if_some_ref, builder, request.query, query);
297
-
298
- // Form options.
299
- apply_option!(set_if_some_ref, builder, request.form, form);
300
-
301
- // JSON options.
302
- apply_option!(set_if_some_ref, builder, request.json, json);
303
-
304
- // Body options.
305
- if let Some(body) = request.body.take() {
306
- builder = builder.body(wreq::Body::from(body));
257
+ // Authentication options.
258
+ apply_option!(
259
+ set_if_some_map_ref,
260
+ builder,
261
+ request.auth,
262
+ auth,
263
+ AsRef::<str>::as_ref
264
+ );
265
+ apply_option!(set_if_some, builder, request.bearer_auth, bearer_auth);
266
+ if let Some(basic_auth) = request.basic_auth.take() {
267
+ builder = builder.basic_auth(basic_auth.0, basic_auth.1);
268
+ }
269
+
270
+ // Allow redirects options.
271
+ match request.allow_redirects {
272
+ Some(false) => {
273
+ builder = builder.redirect(wreq::redirect::Policy::none());
274
+ }
275
+ Some(true) => {
276
+ builder = builder.redirect(
277
+ request
278
+ .max_redirects
279
+ .take()
280
+ .map(wreq::redirect::Policy::limited)
281
+ .unwrap_or_default(),
282
+ );
307
283
  }
284
+ None => {}
285
+ };
286
+
287
+ // Compression options.
288
+ apply_option!(set_if_some, builder, request.gzip, gzip);
289
+ apply_option!(set_if_some, builder, request.brotli, brotli);
290
+ apply_option!(set_if_some, builder, request.deflate, deflate);
291
+ apply_option!(set_if_some, builder, request.zstd, zstd);
292
+
293
+ // Query options.
294
+ apply_option!(set_if_some_ref, builder, request.query, query);
295
+
296
+ // Form options.
297
+ apply_option!(set_if_some_ref, builder, request.form, form);
298
+
299
+ // JSON options.
300
+ apply_option!(set_if_some_ref, builder, request.json, json);
301
+
302
+ // Body options.
303
+ if let Some(body) = request.body.take() {
304
+ builder = builder.body(wreq::Body::from(body));
305
+ }
308
306
 
309
- // Send request.
310
- builder.send().await.map(Response::new)
311
- },
312
- wreq_error,
313
- )
307
+ // Send request.
308
+ builder.send().await.map(Response::new)
309
+ })?
310
+ .map_err(|err| wreq_error(ruby, err))
314
311
  }