Skip to content

Commit 1cde354

Browse files
Merge #4119
4119: Cache proc-macro dlls r=matklad a=edwin0cheng This PR try to fix a deadlock in proc-macro srv by not unloading dlls. Currently we load and unload dlls for each request, however rustc TLS is leaky , such that if we do it a lot of times, all TLS index will be consumed and it will be deadlocked inside panic (it is because panic itself is using TLS too). Co-authored-by: Edwin Cheng <edwin0cheng@gmail.com>
2 parents 12aae77 + bfce657 commit 1cde354

File tree

4 files changed

+110
-65
lines changed

4 files changed

+110
-65
lines changed

crates/ra_proc_macro_srv/src/cli.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
//! Driver for proc macro server
22
3-
use crate::{expand_task, list_macros};
3+
use crate::ProcMacroSrv;
44
use ra_proc_macro::msg::{self, Message};
55
use std::io;
66

77
pub fn run() -> io::Result<()> {
8+
let mut srv = ProcMacroSrv::default();
9+
810
while let Some(req) = read_request()? {
911
let res = match req {
10-
msg::Request::ListMacro(task) => Ok(msg::Response::ListMacro(list_macros(&task))),
12+
msg::Request::ListMacro(task) => srv.list_macros(&task).map(msg::Response::ListMacro),
1113
msg::Request::ExpansionMacro(task) => {
12-
expand_task(&task).map(msg::Response::ExpansionMacro)
14+
srv.expand(&task).map(msg::Response::ExpansionMacro)
1315
}
1416
};
1517

crates/ra_proc_macro_srv/src/dylib.rs

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
33
use crate::{proc_macro::bridge, rustc_server::TokenStream};
44
use std::fs::File;
5-
use std::path::Path;
5+
use std::path::{Path, PathBuf};
66

77
use goblin::{mach::Mach, Object};
88
use libloading::Library;
99
use memmap::Mmap;
1010
use ra_proc_macro::ProcMacroKind;
11-
1211
use std::io;
1312

1413
const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_";
@@ -109,23 +108,21 @@ impl ProcMacroLibraryLibloading {
109108
}
110109
}
111110

112-
type ProcMacroLibraryImpl = ProcMacroLibraryLibloading;
113-
114111
pub struct Expander {
115-
libs: Vec<ProcMacroLibraryImpl>,
112+
inner: ProcMacroLibraryLibloading,
116113
}
117114

118115
impl Expander {
119-
pub fn new(lib: &Path) -> Result<Expander, String> {
116+
pub fn new(lib: &Path) -> io::Result<Expander> {
120117
// Some libraries for dynamic loading require canonicalized path even when it is
121118
// already absolute
122-
let lib = lib
123-
.canonicalize()
124-
.unwrap_or_else(|err| panic!("Cannot canonicalize {}: {:?}", lib.display(), err));
119+
let lib = lib.canonicalize()?;
120+
121+
let lib = ensure_file_with_lock_free_access(&lib)?;
125122

126-
let library = ProcMacroLibraryImpl::open(&lib).map_err(|e| e.to_string())?;
123+
let library = ProcMacroLibraryLibloading::open(&lib)?;
127124

128-
Ok(Expander { libs: vec![library] })
125+
Ok(Expander { inner: library })
129126
}
130127

131128
pub fn expand(
@@ -141,48 +138,46 @@ impl Expander {
141138
TokenStream::with_subtree(attr.clone())
142139
});
143140

144-
for lib in &self.libs {
145-
for proc_macro in &lib.exported_macros {
146-
match proc_macro {
147-
bridge::client::ProcMacro::CustomDerive { trait_name, client, .. }
148-
if *trait_name == macro_name =>
149-
{
150-
let res = client.run(
151-
&crate::proc_macro::bridge::server::SameThread,
152-
crate::rustc_server::Rustc::default(),
153-
parsed_body,
154-
);
155-
return res.map(|it| it.subtree);
156-
}
157-
bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => {
158-
let res = client.run(
159-
&crate::proc_macro::bridge::server::SameThread,
160-
crate::rustc_server::Rustc::default(),
161-
parsed_body,
162-
);
163-
return res.map(|it| it.subtree);
164-
}
165-
bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => {
166-
let res = client.run(
167-
&crate::proc_macro::bridge::server::SameThread,
168-
crate::rustc_server::Rustc::default(),
169-
parsed_attributes,
170-
parsed_body,
171-
);
172-
return res.map(|it| it.subtree);
173-
}
174-
_ => continue,
141+
for proc_macro in &self.inner.exported_macros {
142+
match proc_macro {
143+
bridge::client::ProcMacro::CustomDerive { trait_name, client, .. }
144+
if *trait_name == macro_name =>
145+
{
146+
let res = client.run(
147+
&crate::proc_macro::bridge::server::SameThread,
148+
crate::rustc_server::Rustc::default(),
149+
parsed_body,
150+
);
151+
return res.map(|it| it.subtree);
152+
}
153+
bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => {
154+
let res = client.run(
155+
&crate::proc_macro::bridge::server::SameThread,
156+
crate::rustc_server::Rustc::default(),
157+
parsed_body,
158+
);
159+
return res.map(|it| it.subtree);
160+
}
161+
bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => {
162+
let res = client.run(
163+
&crate::proc_macro::bridge::server::SameThread,
164+
crate::rustc_server::Rustc::default(),
165+
parsed_attributes,
166+
parsed_body,
167+
);
168+
return res.map(|it| it.subtree);
175169
}
170+
_ => continue,
176171
}
177172
}
178173

179174
Err(bridge::PanicMessage::String("Nothing to expand".to_string()))
180175
}
181176

182177
pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
183-
self.libs
178+
self.inner
179+
.exported_macros
184180
.iter()
185-
.flat_map(|it| &it.exported_macros)
186181
.map(|proc_macro| match proc_macro {
187182
bridge::client::ProcMacro::CustomDerive { trait_name, .. } => {
188183
(trait_name.to_string(), ProcMacroKind::CustomDerive)
@@ -197,3 +192,33 @@ impl Expander {
197192
.collect()
198193
}
199194
}
195+
196+
/// Copy the dylib to temp directory to prevent locking in Windows
197+
#[cfg(windows)]
198+
fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<PathBuf> {
199+
use std::{ffi::OsString, time::SystemTime};
200+
201+
let mut to = std::env::temp_dir();
202+
203+
let file_name = path.file_name().ok_or_else(|| {
204+
io::Error::new(
205+
io::ErrorKind::InvalidInput,
206+
format!("File path is invalid: {}", path.display()),
207+
)
208+
})?;
209+
210+
// generate a time deps unique number
211+
let t = SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("Time went backwards");
212+
213+
let mut unique_name = OsString::from(t.as_millis().to_string());
214+
unique_name.push(file_name);
215+
216+
to.push(unique_name);
217+
std::fs::copy(path, &to).unwrap();
218+
Ok(to)
219+
}
220+
221+
#[cfg(unix)]
222+
fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<PathBuf> {
223+
Ok(path.to_path_buf())
224+
}

crates/ra_proc_macro_srv/src/lib.rs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,46 @@ mod dylib;
2121

2222
use proc_macro::bridge::client::TokenStream;
2323
use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask};
24-
use std::path::Path;
24+
use std::{
25+
collections::{hash_map::Entry, HashMap},
26+
fs,
27+
path::{Path, PathBuf},
28+
time::SystemTime,
29+
};
2530

26-
pub(crate) fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> {
27-
let expander = create_expander(&task.lib);
31+
#[derive(Default)]
32+
pub(crate) struct ProcMacroSrv {
33+
expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>,
34+
}
2835

29-
match expander.expand(&task.macro_name, &task.macro_body, task.attributes.as_ref()) {
30-
Ok(expansion) => Ok(ExpansionResult { expansion }),
31-
Err(msg) => {
32-
Err(format!("Cannot perform expansion for {}: error {:?}", &task.macro_name, msg))
36+
impl ProcMacroSrv {
37+
pub fn expand(&mut self, task: &ExpansionTask) -> Result<ExpansionResult, String> {
38+
let expander = self.expander(&task.lib)?;
39+
match expander.expand(&task.macro_name, &task.macro_body, task.attributes.as_ref()) {
40+
Ok(expansion) => Ok(ExpansionResult { expansion }),
41+
Err(msg) => {
42+
Err(format!("Cannot perform expansion for {}: error {:?}", &task.macro_name, msg))
43+
}
3344
}
3445
}
35-
}
3646

37-
pub(crate) fn list_macros(task: &ListMacrosTask) -> ListMacrosResult {
38-
let expander = create_expander(&task.lib);
47+
pub fn list_macros(&mut self, task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
48+
let expander = self.expander(&task.lib)?;
49+
Ok(ListMacrosResult { macros: expander.list_macros() })
50+
}
3951

40-
ListMacrosResult { macros: expander.list_macros() }
41-
}
52+
fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> {
53+
let time = fs::metadata(path).and_then(|it| it.modified()).map_err(|err| {
54+
format!("Failed to get file metadata for {}: {:?}", path.display(), err)
55+
})?;
4256

43-
fn create_expander(lib: &Path) -> dylib::Expander {
44-
dylib::Expander::new(lib)
45-
.unwrap_or_else(|err| panic!("Cannot create expander for {}: {:?}", lib.display(), err))
57+
Ok(match self.expanders.entry((path.to_path_buf(), time)) {
58+
Entry::Vacant(v) => v.insert(dylib::Expander::new(path).map_err(|err| {
59+
format!("Cannot create expander for {}: {:?}", path.display(), err)
60+
})?),
61+
Entry::Occupied(e) => e.into_mut(),
62+
})
63+
}
4664
}
4765

4866
pub mod cli;

crates/ra_proc_macro_srv/src/tests/utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! utils used in proc-macro tests
22
33
use crate::dylib;
4-
use crate::list_macros;
4+
use crate::ProcMacroSrv;
55
pub use difference::Changeset as __Changeset;
66
use ra_proc_macro::ListMacrosTask;
77
use std::str::FromStr;
@@ -59,7 +59,7 @@ pub fn assert_expand(
5959
pub fn list(crate_name: &str, version: &str) -> Vec<String> {
6060
let path = fixtures::dylib_path(crate_name, version);
6161
let task = ListMacrosTask { lib: path };
62-
63-
let res = list_macros(&task);
62+
let mut srv = ProcMacroSrv::default();
63+
let res = srv.list_macros(&task).unwrap();
6464
res.macros.into_iter().map(|(name, kind)| format!("{} [{:?}]", name, kind)).collect()
6565
}

0 commit comments

Comments
 (0)