Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use no-std-compat to transition to no-std #64

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ jobs:
- run:
name: Run all tests
command: cargo test --all
- run:
name: Run all tests (no-std version)
command: cargo test --all --no-default-features
rust/coverage:
machine: true
steps:
Expand Down
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ shredder_derive = { git = "https://github.com/Others/shredder_derive.git" }
#shredder_derive = { path = "../shredder_derive" }
stable_deref_trait = "1.1"

[dependencies.no-std-compat]
# Waiting for stable version containing new `sync` version
git = "https://gitlab.com/jD91mZM2/no-std-compat"
features = [ "alloc", "compat_hash", "compat_sync", "compat_macros" ]

[dev-dependencies]
paste = "1.0"
rand = "0.7.3"
Expand All @@ -35,5 +40,7 @@ trybuild = "1.0"
#debug = true

[features]
default = []
default = [ "std", "threads" ] # Default to using the std
std = [ "no-std-compat/std" ]
threads = [ "std" ]
nightly-features = []
1 change: 1 addition & 0 deletions src/atomic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::marker::PhantomData;
use std::mem;
use std::prelude::v1::*;
use std::ptr::drop_in_place;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::Arc;
Expand Down
4 changes: 4 additions & 0 deletions src/collector/alloc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::alloc::{alloc, dealloc, Layout};
use std::mem::{self, ManuallyDrop};

#[cfg(feature = "std")]
use std::panic::UnwindSafe;
use std::prelude::v1::*;
use std::ptr;

use crate::collector::InternalGcRef;
Expand All @@ -27,6 +30,7 @@ pub enum DeallocationAction {
// It also, by contract of Scan, cannot have a Drop method that is unsafe in any thead
unsafe impl Send for GcAllocation {}
// Therefore, GcDataPtr is also UnwindSafe in the context we need it to be
#[cfg(feature = "std")]
impl UnwindSafe for GcAllocation {}
// We use the lockout to ensure that `GcDataPtr`s are not shared
unsafe impl Sync for GcAllocation {}
Expand Down
5 changes: 2 additions & 3 deletions src/collector/collect_impl.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::prelude::v1::*;
use std::sync::atomic::Ordering;

use crossbeam::deque::Injector;
Expand Down Expand Up @@ -145,9 +146,7 @@ impl Collector {

// Send off the data to be dropped in the background
let drop_msg = DropMessage::DataToDrop(to_drop);
if let Err(e) = self.dropper.send_msg(drop_msg) {
error!("Error sending to drop thread {}", e);
}
self.drop(drop_msg);

// update the trigger based on the new baseline
self.trigger
Expand Down
1 change: 1 addition & 0 deletions src/collector/data.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::prelude::v1::*;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use std::sync::Arc;

Expand Down
108 changes: 74 additions & 34 deletions src/collector/dropper.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
use std::prelude::v1::*;

#[cfg(feature = "std")]
use std::panic::catch_unwind;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::thread::spawn;

use crossbeam::channel::{self, SendError, Sender};
use crossbeam::channel::{SendError, Sender};
use parking_lot::RwLock;
use rayon::iter::IntoParallelRefIterator;
use rayon::iter::ParallelIterator;

#[cfg(feature = "threads")]
use std::thread::spawn;

use crate::collector::GcData;

pub(crate) struct BackgroundDropper {
#[cfg(feature = "threads")]
sender: Sender<DropMessage>,
}

Expand All @@ -23,46 +29,80 @@ pub(crate) enum DropMessage {

impl BackgroundDropper {
pub fn new() -> Self {
let (sender, receiver) = channel::unbounded();
#[cfg(feature = "threads")]
let (sender, receiver) = crossbeam::channel::unbounded();

// The drop thread deals with doing all the Drops this collector needs to do
spawn(move || {
#[cfg(feature = "threads")]
spawn(Box::new(move || {
// An Err value means the stream will never recover
while let Ok(drop_msg) = receiver.recv() {
match drop_msg {
DropMessage::DataToDrop(to_drop) => {
let to_drop = to_drop.read();

// NOTE: It's important that all data is correctly marked as deallocated before we start
to_drop.par_iter().for_each(|data| {
// Mark this data as in the process of being deallocated and unsafe to access
data.deallocated.store(true, Ordering::SeqCst);
});

// Then run the drops if needed
to_drop.par_iter().for_each(|data| {
let underlying_allocation = data.underlying_allocation;
let res = catch_unwind(move || unsafe {
underlying_allocation.deallocate();
});
if let Err(e) = res {
eprintln!("Gc background drop failed: {:?}", e);
}
});
}
DropMessage::SyncUp(responder) => {
if let Err(e) = responder.send(()) {
eprintln!("Gc background syncup failed: {:?}", e);
}
}
}
handle_message(drop_msg)
}
});
}));

Self { sender }
Self {
#[cfg(feature = "threads")]
sender,
}
}

pub fn send_msg(&self, msg: DropMessage) -> Result<(), SendError<DropMessage>> {
self.sender.send(msg)
#[cfg(feature = "threads")]
{
self.sender.send(msg)
}

#[cfg(not(feature = "threads"))]
{
handle_message(msg);
Ok(())
}
}
}

fn handle_message(drop_msg: DropMessage) {
match drop_msg {
DropMessage::DataToDrop(to_drop) => {
let to_drop = to_drop.read();

// NOTE: It's important that all data is correctly marked as deallocated before we start
to_drop.par_iter().for_each(|data| {
// Mark this data as in the process of being deallocated and unsafe to access
data.deallocated.store(true, Ordering::SeqCst);
});

// Then run the drops if needed
to_drop.par_iter().for_each(|data| {
let underlying_allocation = data.underlying_allocation;

// When the stdlib is available, we can use catch_unwind
// to protect ourselves against panics that unwind.
#[cfg(feature = "std")]
{
let res = catch_unwind(move || unsafe {
underlying_allocation.deallocate();
});
if let Err(e) = res {
eprintln!("Gc background drop failed: {:?}", e);
}
}

// When it is not available, however, panics probably
// won't unwind, and there's no safe means to catch
// a panic.
//
// TODO is there a better way to safely handle this?
#[cfg(not(feature = "std"))]
unsafe {
underlying_allocation.deallocate()
};
});
}
DropMessage::SyncUp(responder) => {
if let Err(e) = responder.send(()) {
eprintln!("Gc background syncup failed: {:?}", e);
}
}
}
}
45 changes: 35 additions & 10 deletions src/collector/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::prelude::v1::*;

mod alloc;
mod collect_impl;
mod data;
Expand All @@ -6,9 +8,13 @@ mod trigger;

use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use std::sync::Arc;

#[cfg(feature = "threads")]
use std::thread::spawn;

use crossbeam::channel::{self, Sender};
use crossbeam::channel;
#[cfg(feature = "threads")]
use crossbeam::channel::Sender;
use once_cell::sync::Lazy;
use parking_lot::Mutex;

Expand Down Expand Up @@ -68,6 +74,7 @@ pub struct Collector {
/// we run automatic gc in a background thread
/// sending to this channel indicates that thread should check the trigger, then collect if the
/// trigger indicates it should
#[cfg(feature = "threads")]
async_gc_notifier: Sender<()>,
/// all the data we are managing plus metadata about what `Gc<T>`s exist
tracked_data: TrackedData,
Expand All @@ -88,13 +95,15 @@ struct TrackedData {

impl Collector {
fn new() -> Arc<Self> {
#[cfg(feature = "threads")]
let (async_gc_notifier, async_gc_receiver) = channel::bounded(1);

let res = Arc::new(Self {
gc_lock: Mutex::default(),
atomic_spinlock: AtomicProtectingSpinlock::default(),
trigger: GcTrigger::default(),
dropper: BackgroundDropper::new(),
#[cfg(feature = "threads")]
async_gc_notifier,
tracked_data: TrackedData {
// This is janky, but we subtract one from the collection number
Expand All @@ -109,21 +118,31 @@ impl Collector {
},
});

// The async Gc thread deals with background Gc'ing
let async_collector_ref = Arc::downgrade(&res);
spawn(move || {
// An Err value means the stream will never recover
while async_gc_receiver.recv().is_ok() {
if let Some(collector) = async_collector_ref.upgrade() {
collector.check_then_collect();
#[cfg(feature = "threads")]
{
// The async Gc thread deals with background Gc'ing
let async_collector_ref = Arc::downgrade(&res);
spawn(move || {
// An Err value means the stream will never recover
while async_gc_receiver.recv().is_ok() {
if let Some(collector) = async_collector_ref.upgrade() {
collector.check_then_collect();
}
}
}
});
});
}

res
}

fn drop(&self, drop_msg: DropMessage) {
if let Err(e) = self.dropper.send_msg(drop_msg) {
error!("Error sending to drop thread {}", e);
}
}

#[inline]
#[cfg(feature = "threads")]
fn notify_async_gc_thread(&self) {
// Note: We only send if there is room in the channel
// If there's already a notification there the async thread is already notified
Expand All @@ -137,6 +156,12 @@ impl Collector {
};
}

#[inline]
#[cfg(not(feature = "threads"))]
fn notify_async_gc_thread(&self) {
self.check_then_collect();
}

pub fn track_with_drop<T: Scan + GcDrop>(&self, data: T) -> (InternalGcRef, *const T) {
let (gc_data_ptr, heap_ptr) = GcAllocation::allocate_with_drop(data);
self.track(gc_data_ptr, heap_ptr)
Expand Down
1 change: 1 addition & 0 deletions src/collector/trigger.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use parking_lot::Mutex;
use std::prelude::v1::*;

// TODO(issue): https://github.com/Others/shredder/issues/8
const DEFAULT_ALLOCATION_TRIGGER_PERCENT: f32 = 0.75;
Expand Down
7 changes: 7 additions & 0 deletions src/concurrency/atomic_protection.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::prelude::v1::*;
use std::sync::atomic::{AtomicU64, Ordering};

#[cfg(feature = "threads")]
use std::thread::yield_now;

const SENTINEL_VALUE: u64 = 1 << 60;
Expand Down Expand Up @@ -38,6 +41,10 @@ impl AtomicProtectingSpinlock {
}

// Try to be kind to our scheduler, even as we employ an anti-pattern
//
// Without threading support, we'll just have to busy-wait.
// Should we let the user supply a 'yield' function of their own?
#[cfg(feature = "threads")]
yield_now()
}
}
Expand Down
1 change: 1 addition & 0 deletions src/concurrency/chunked_ll.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::mem::{self, MaybeUninit};
use std::prelude::v1::*;
use std::ptr;
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::sync::Arc;
Expand Down
1 change: 1 addition & 0 deletions src/concurrency/lockout.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::prelude::v1::*;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;

Expand Down
9 changes: 8 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@
clippy::cast_precision_loss, // There is no way to avoid this precision loss
clippy::explicit_deref_methods, // Sometimes calling `deref` directly is clearer
clippy::module_name_repetitions, // Sometimes clear naming calls for repetition
clippy::multiple_crate_versions // There is no way to easily fix this without modifying our dependencies
clippy::multiple_crate_versions, // There is no way to easily fix this without modifying our dependencies
clippy::wildcard_imports, // No-std compatibility layer requieres these for ergonomics
)]
#![no_std]

extern crate no_std_compat as std;

#[cfg(feature = "std")]
#[macro_use]
extern crate crossbeam;

Expand Down Expand Up @@ -70,6 +75,8 @@ pub mod wrappers;
use std::cell::RefCell;
use std::sync::{Mutex, RwLock};

use std::prelude::v1::*;

use crate::collector::COLLECTOR;

pub use crate::finalize::Finalize;
Expand Down
2 changes: 2 additions & 0 deletions src/marker/gc_deref.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::prelude::v1::*;

/// A marker trait that marks that this data can be stored in a `DerefGc`
///
/// `T` can be `GcDeref` only if it is deeply immutable through a `&T`. This is because it's
Expand Down
1 change: 1 addition & 0 deletions src/marker/gc_safe.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::prelude::v1::*;

/// A marker trait that marks that data can be scanned in the background by the garbage collector.
///
Expand Down
1 change: 1 addition & 0 deletions src/r.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::prelude::v1::*;

use crate::marker::{GcDeref, GcDrop, GcSafe};
use crate::{Finalize, Scan, Scanner};
Expand Down
Loading