diff --git a/Cargo.lock b/Cargo.lock index 054d460..7e3ca67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "byteorder" version = "1.4.2" @@ -27,10 +33,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + [[package]] name = "cstree" version = "0.0.2" dependencies = [ + "crossbeam-utils", "fxhash", "lasso", "m_lexer", diff --git a/Cargo.toml b/Cargo.toml index 62ca9cf..07dfbfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ features = ["derive"] m_lexer = "0.0.4" serde_json = "1.0.61" serde_test = "1.0.119" +crossbeam-utils = "0.8" [features] default = [] diff --git a/src/syntax.rs b/src/syntax.rs index 1739a4a..0b2966d 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -38,6 +38,9 @@ pub struct SyntaxNode { data: *mut NodeData, } +unsafe impl Send for SyntaxNode {} +unsafe impl Sync for SyntaxNode {} + impl SyntaxNode { pub fn debug(&self, resolver: &impl Resolver, recursive: bool) -> String { // NOTE: `fmt::Write` methods on `String` never fail diff --git a/tests/sendsync.rs b/tests/sendsync.rs new file mode 100644 index 0000000..6886c3c --- /dev/null +++ b/tests/sendsync.rs @@ -0,0 +1,153 @@ +#![allow(clippy::redundant_clone)] + +#[allow(unused)] +mod common; + +use crossbeam_utils::thread::scope; +use std::{thread, time::Duration}; + +use common::{build_recursive, Element, SyntaxNode}; +use cstree::GreenNodeBuilder; +use lasso::Resolver; + +fn build_tree(root: &Element<'_>) -> SyntaxNode { + let mut builder = GreenNodeBuilder::new(); + build_recursive(root, &mut builder, 0); + let (node, interner) = builder.finish(); + SyntaxNode::new_root_with_resolver(node, interner.unwrap().into_resolver()) +} + +fn two_level_tree() -> Element<'static> { + use Element::*; + Node(vec![ + Node(vec![Token("0.0"), Token("0.1")]), + Node(vec![Token("1.0")]), + Node(vec![Token("2.0"), Token("2.1"), Token("2.2")]), + ]) +} + +#[test] +#[cfg_attr(miri, ignore)] +fn send() { + let tree = two_level_tree(); + let tree = build_tree::<()>(&tree); + let thread_tree = tree.clone(); + let thread = thread::spawn(move || { + let leaf1_0 = thread_tree + .children() + .nth(1) + .unwrap() + .children_with_tokens() + .next() + .unwrap(); + let leaf1_0 = leaf1_0.into_token().unwrap(); + leaf1_0.resolve_text(thread_tree.resolver().as_ref()).to_string() + }); + assert_eq!(thread.join().unwrap(), "1.0"); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn send_data() { + let tree = two_level_tree(); + let tree = build_tree::(&tree); + let thread_tree = tree.clone(); + { + let node2 = tree.children().nth(2).unwrap(); + assert_eq!(*node2.try_set_data("data".into()).unwrap(), "data"); + let data = node2.get_data().unwrap(); + assert_eq!(data.as_str(), "data"); + node2.set_data("payload".into()); + let data = node2.get_data().unwrap(); + assert_eq!(data.as_str(), "payload"); + } + let t = thread::spawn(move || { + let node2 = thread_tree.children().nth(2).unwrap(); + assert!(node2.try_set_data("already present".into()).is_err()); + let data = node2.get_data().unwrap(); + assert_eq!(data.as_str(), "payload"); + node2.set_data("new data".into()); + }); + // wait for t to finish + t.join().unwrap(); + { + let node2 = tree.children().nth(2).unwrap(); + let data = node2.get_data().unwrap(); + assert_eq!(data.as_str(), "new data"); + node2.clear_data(); + // re-use `data` after node data was cleared + assert_eq!(data.as_str(), "new data"); + } + let thread_tree = tree.clone(); + thread::spawn(move || { + let node2 = thread_tree.children().nth(2).unwrap(); + assert_eq!(node2.get_data(), None); + }) + .join() + .unwrap(); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn sync() { + let tree = two_level_tree(); + let tree = build_tree::<()>(&tree); + let thread_tree = &tree; + let result = scope(move |s| { + s.spawn(move |_| { + let leaf1_0 = thread_tree + .children() + .nth(1) + .unwrap() + .children_with_tokens() + .next() + .unwrap(); + let leaf1_0 = leaf1_0.into_token().unwrap(); + leaf1_0.resolve_text(thread_tree.resolver().as_ref()).to_string() + }) + .join() + .unwrap() + }); + assert_eq!(result.unwrap(), "1.0"); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn drop_send() { + let tree = two_level_tree(); + let tree = build_tree::<()>(&tree); + let thread_tree = tree.clone(); + let thread = thread::spawn(move || { + drop(thread_tree); + }); + thread.join().unwrap(); + thread::sleep(Duration::from_millis(500)); + drop(tree); + + let tree = two_level_tree(); + let tree = build_tree::<()>(&tree); + let thread_tree = tree.clone(); + drop(tree); + let thread = thread::spawn(move || { + thread::sleep(Duration::from_millis(500)); + drop(thread_tree); + }); + thread.join().unwrap(); +} + +#[test] +#[cfg_attr(miri, ignore)] +#[allow(clippy::drop_ref)] +fn drop_sync() { + let tree = two_level_tree(); + let tree = build_tree::<()>(&tree); + let thread_tree = &tree; + scope(move |s| { + s.spawn(move |_| { + drop(thread_tree); + }); + }) + .unwrap(); + thread::sleep(Duration::from_millis(500)); + drop(tree); +}