Skip to content

Commit 4c1a817

Browse files
committed
Backport #281
1 parent 9f45efe commit 4c1a817

File tree

9 files changed

+141
-48
lines changed

9 files changed

+141
-48
lines changed

CHANGELOG.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,17 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7-
## Unreleased
7+
## [0.5.2] - 2018-11-26
8+
9+
### Fixed
10+
11+
* Fix undeterministic segfaults when creating many objects by kngwyu in [#281](https://github.com/PyO3/pyo3/pull/281)
12+
13+
## 0.5.1 - 2018-11-24
14+
15+
Yanked
16+
17+
## [0.5.0] - 2018-11-11
818

919
### Added
1020

@@ -20,6 +30,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2030
* Slowly removing specialization uses
2131
* `PyString`, `PyUnicode`, and `PyBytes` no longer have a `data()` method
2232
(replaced by `as_bytes()`) and `PyStringData` has been removed.
33+
* The pyobject_extract macro
2334

2435
### Changed
2536
* Removes the types from the root module and the prelude. They now live in `pyo3::types` instead.
@@ -31,18 +42,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3142
* Updated to syn 0.15
3243
* Splitted `PyTypeObject` into `PyTypeObject` without the create method and `PyTypeCreate` with requires `PyObjectAlloc<Self> + PyTypeInfo + Sized`.
3344
* Ran `cargo edition --fix` which prefixed path with `crate::` for rust 2018
34-
* Renamed `async` to `pyasync` as async will be a keyword in the 2018 edition.
45+
* Renamed `async` to `pyasync` as async will be a keyword in the 2018 edition.
46+
* Starting to use `NonNull<*mut PyObject>` for Py and PyObject by ijl [#260](https://github.com/PyO3/pyo3/pull/260)
3547

3648
### Fixed
3749

3850
* Added an explanation that the GIL can temporarily be released even while holding a GILGuard.
3951
* Lots of clippy errors
4052
* Fix segfault on calling an unknown method on a PyObject
4153
* Work around a [bug](https://github.com/rust-lang/rust/issues/55380) in the rust compiler by kngwyu [#252](https://github.com/PyO3/pyo3/pull/252)
42-
43-
### Removed
44-
45-
* The pyobject_extract macro
54+
* Fixed a segfault with subclassing pyo3 create classes and using `__class__` by kngwyu [#263](https://github.com/PyO3/pyo3/pull/263)
4655

4756
## [0.4.1] - 2018-08-20
4857

@@ -187,6 +196,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
187196

188197
* Initial release
189198

199+
[0.5.2]: https://github.com/pyo3/pyo3/compare/v0.5.0...v0.5.2
200+
[0.5.0]: https://github.com/pyo3/pyo3/compare/v0.4.1...v0.5.0
190201
[0.4.1]: https://github.com/pyo3/pyo3/compare/v0.4.0...v0.4.1
191202
[0.4.0]: https://github.com/pyo3/pyo3/compare/v0.3.2...v0.4.0
192203
[0.3.2]: https://github.com/pyo3/pyo3/compare/v0.3.1...v0.3.2

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyo3"
3-
version = "0.5.0-alpha.3"
3+
version = "0.5.2"
44
description = "Bindings to Python interpreter"
55
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
66
readme = "README.md"
@@ -22,7 +22,7 @@ codecov = { repository = "PyO3/pyo3", branch = "master", service = "github" }
2222
libc = "0.2.43"
2323
spin = "0.4.9"
2424
num-traits = "0.2.6"
25-
pyo3cls = { path = "pyo3cls", version = "=0.5.0-alpha.3" }
25+
pyo3cls = { path = "pyo3cls", version = "=0.5.2" }
2626
mashup = "0.1.9"
2727
num-complex = { version = "0.2.1", optional = true }
2828

benches/bench_dict.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![feature(test)]
2+
extern crate pyo3;
3+
extern crate test;
4+
use test::Bencher;
5+
6+
use pyo3::{prelude::*, types::IntoPyDict};
7+
8+
#[bench]
9+
fn iter_dict(b: &mut Bencher) {
10+
let gil = Python::acquire_gil();
11+
let py = gil.python();
12+
const LEN: usize = 1_000_00;
13+
let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py);
14+
let mut sum = 0;
15+
b.iter(|| {
16+
for (k, _v) in dict.iter() {
17+
let i: u64 = k.extract().unwrap();
18+
sum += i;
19+
}
20+
});
21+
}

pyo3-derive-backend/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyo3-derive-backend"
3-
version = "0.5.0-alpha.3"
3+
version = "0.5.2"
44
description = "Code generation for PyO3 package"
55
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
66
keywords = ["pyo3", "python", "cpython", "ffi"]

pyo3cls/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyo3cls"
3-
version = "0.5.0-alpha.3"
3+
version = "0.5.2"
44
description = "Proc macros for PyO3 package"
55
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
66
keywords = ["pyo3", "python", "cpython", "ffi"]
@@ -16,4 +16,4 @@ proc-macro = true
1616
quote= "0.6.9"
1717
proc-macro2 = "0.4.20"
1818
syn = { version = "0.15.15", features = ["full", "extra-traits"] }
19-
pyo3-derive-backend = { path = "../pyo3-derive-backend", version = "=0.5.0-alpha.3" }
19+
pyo3-derive-backend = { path = "../pyo3-derive-backend", version = "=0.5.2" }

src/instance.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ impl<T> PartialEq for Py<T> {
298298
impl<T> Drop for Py<T> {
299299
fn drop(&mut self) {
300300
unsafe {
301-
pythonrun::register_pointer(self.0);
301+
pythonrun::register_pointer(self.0.as_ptr());
302302
}
303303
}
304304
}

src/object.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ impl<'a> FromPyObject<'a> for PyObject {
316316
impl Drop for PyObject {
317317
fn drop(&mut self) {
318318
unsafe {
319-
pythonrun::register_pointer(self.0);
319+
pythonrun::register_pointer(self.0.as_ptr());
320320
}
321321
}
322322
}

src/pythonrun.rs

Lines changed: 79 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) 2017-present PyO3 Project and Contributors
22
use crate::ffi;
3-
use crate::python::{NonNullPyObject, Python};
3+
use crate::python::Python;
44
use crate::types::PyObjectRef;
55
use spin;
66
use std::{any, marker, rc, sync};
@@ -107,8 +107,8 @@ impl Drop for GILGuard {
107107

108108
/// Release pool
109109
struct ReleasePool {
110-
owned: Vec<*mut ffi::PyObject>,
111-
borrowed: Vec<*mut ffi::PyObject>,
110+
owned: ArrayList<*mut ffi::PyObject>,
111+
borrowed: ArrayList<*mut ffi::PyObject>,
112112
pointers: *mut Vec<*mut ffi::PyObject>,
113113
obj: Vec<Box<any::Any>>,
114114
p: spin::Mutex<*mut Vec<*mut ffi::PyObject>>,
@@ -117,8 +117,8 @@ struct ReleasePool {
117117
impl ReleasePool {
118118
fn new() -> ReleasePool {
119119
ReleasePool {
120-
owned: Vec::with_capacity(256),
121-
borrowed: Vec::with_capacity(256),
120+
owned: ArrayList::new(),
121+
borrowed: ArrayList::new(),
122122
pointers: Box::into_raw(Box::new(Vec::with_capacity(256))),
123123
obj: Vec::with_capacity(8),
124124
p: spin::Mutex::new(Box::into_raw(Box::new(Vec::with_capacity(256)))),
@@ -127,39 +127,30 @@ impl ReleasePool {
127127

128128
unsafe fn release_pointers(&mut self) {
129129
let mut v = self.p.lock();
130-
131-
// vec of pointers
132-
let ptr = *v;
133-
let vec: &'static mut Vec<*mut ffi::PyObject> = &mut *ptr;
130+
let vec = &mut **v;
134131
if vec.is_empty() {
135132
return;
136133
}
137134

138135
// switch vectors
139-
*v = self.pointers;
140-
self.pointers = ptr;
136+
std::mem::swap(&mut self.pointers, &mut *v);
141137
drop(v);
142138

143-
// release py objects
139+
// release PyObjects
144140
for ptr in vec.iter_mut() {
145141
ffi::Py_DECREF(*ptr);
146142
}
147143
vec.set_len(0);
148144
}
149145

150146
pub unsafe fn drain(&mut self, owned: usize, borrowed: usize, pointers: bool) {
151-
let len = self.owned.len();
152-
if owned < len {
153-
for ptr in &mut self.owned[owned..len] {
154-
ffi::Py_DECREF(*ptr);
155-
}
156-
self.owned.set_len(owned);
157-
}
158-
159-
let len = self.borrowed.len();
160-
if borrowed < len {
161-
self.borrowed.set_len(borrowed);
147+
// Release owned objects(call decref)
148+
while owned < self.owned.len() {
149+
let last = self.owned.pop_back().unwrap();
150+
ffi::Py_DECREF(last);
162151
}
152+
// Release borrowed objects(don't call decref)
153+
self.borrowed.truncate(borrowed);
163154

164155
if pointers {
165156
self.release_pointers();
@@ -230,24 +221,19 @@ pub unsafe fn register_any<'p, T: 'static>(obj: T) -> &'p T {
230221
.unwrap()
231222
}
232223

233-
pub unsafe fn register_pointer(obj: NonNullPyObject) {
234-
let pool: &'static mut ReleasePool = &mut *POOL;
235-
236-
let mut v = pool.p.lock();
237-
let pool: &'static mut Vec<*mut ffi::PyObject> = &mut *(*v);
238-
pool.push(obj.as_ptr());
224+
pub unsafe fn register_pointer(obj: *mut ffi::PyObject) {
225+
let pool = &mut *POOL;
226+
(**pool.p.lock()).push(obj);
239227
}
240228

241229
pub unsafe fn register_owned(_py: Python, obj: *mut ffi::PyObject) -> &PyObjectRef {
242-
let pool: &'static mut ReleasePool = &mut *POOL;
243-
pool.owned.push(obj);
244-
&*(&pool.owned[pool.owned.len() - 1] as *const *mut ffi::PyObject as *const PyObjectRef)
230+
let pool = &mut *POOL;
231+
&*(pool.owned.push_back(obj) as *const _ as *const PyObjectRef)
245232
}
246233

247234
pub unsafe fn register_borrowed(_py: Python, obj: *mut ffi::PyObject) -> &PyObjectRef {
248-
let pool: &'static mut ReleasePool = &mut *POOL;
249-
pool.borrowed.push(obj);
250-
&*(&pool.borrowed[pool.borrowed.len() - 1] as *const *mut ffi::PyObject as *const PyObjectRef)
235+
let pool = &mut *POOL;
236+
&*(pool.borrowed.push_back(obj) as *const _ as *const PyObjectRef)
251237
}
252238

253239
impl GILGuard {
@@ -277,6 +263,64 @@ impl GILGuard {
277263
}
278264
}
279265

266+
use self::array_list::ArrayList;
267+
268+
mod array_list {
269+
use std::collections::LinkedList;
270+
use std::mem;
271+
272+
const BLOCK_SIZE: usize = 256;
273+
274+
/// A container type for Release Pool
275+
/// See #271 for why this is crated
276+
pub(super) struct ArrayList<T> {
277+
inner: LinkedList<[T; BLOCK_SIZE]>,
278+
length: usize,
279+
}
280+
281+
impl<T: Clone> ArrayList<T> {
282+
pub fn new() -> Self {
283+
ArrayList {
284+
inner: LinkedList::new(),
285+
length: 0,
286+
}
287+
}
288+
pub fn push_back(&mut self, item: T) -> &T {
289+
let next_idx = self.next_idx();
290+
if next_idx == 0 {
291+
self.inner.push_back(unsafe { mem::uninitialized() });
292+
}
293+
self.inner.back_mut().unwrap()[next_idx] = item;
294+
self.length += 1;
295+
&self.inner.back().unwrap()[next_idx]
296+
}
297+
pub fn pop_back(&mut self) -> Option<T> {
298+
self.length -= 1;
299+
let current_idx = self.next_idx();
300+
if self.length >= BLOCK_SIZE && current_idx == 0 {
301+
let last_list = self.inner.pop_back()?;
302+
return Some(last_list[0].clone());
303+
}
304+
self.inner.back().map(|arr| arr[current_idx].clone())
305+
}
306+
pub fn len(&self) -> usize {
307+
self.length
308+
}
309+
pub fn truncate(&mut self, new_len: usize) {
310+
if self.length <= new_len {
311+
return;
312+
}
313+
while self.inner.len() > (new_len + BLOCK_SIZE - 1) / BLOCK_SIZE {
314+
self.inner.pop_back();
315+
}
316+
self.length = new_len;
317+
}
318+
fn next_idx(&self) -> usize {
319+
self.length % BLOCK_SIZE
320+
}
321+
}
322+
}
323+
280324
#[cfg(test)]
281325
mod test {
282326
use super::{GILPool, ReleasePool, POOL};

tests/test_dict_iter.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
extern crate pyo3;
2+
3+
use pyo3::{prelude::*, types::IntoPyDict};
4+
5+
#[test]
6+
fn iter_dict_nosegv() {
7+
let gil = Python::acquire_gil();
8+
let py = gil.python();
9+
const LEN: usize = 10_000_000;
10+
let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py);
11+
let mut sum = 0;
12+
for (k, _v) in dict.iter() {
13+
let i: u64 = k.extract().unwrap();
14+
sum += i;
15+
}
16+
assert_eq!(sum, 49999995000000);
17+
}

0 commit comments

Comments
 (0)