aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yaml1
-rw-r--r--Cargo.lock5
-rw-r--r--crates/ra_cargo_watch/Cargo.toml1
-rw-r--r--crates/ra_cargo_watch/src/lib.rs61
-rw-r--r--crates/ra_prof/src/lib.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop.rs55
-rw-r--r--crates/rust-analyzer/src/world.rs15
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs3
8 files changed, 63 insertions, 86 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 5050c558c..fb7afe9fd 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -11,6 +11,7 @@ env:
11 CARGO_INCREMENTAL: 0 11 CARGO_INCREMENTAL: 0
12 CARGO_NET_RETRY: 10 12 CARGO_NET_RETRY: 10
13 RUN_SLOW_TESTS: 1 13 RUN_SLOW_TESTS: 1
14 RUST_BACKTRACE: short
14 RUSTFLAGS: -D warnings 15 RUSTFLAGS: -D warnings
15 RUSTUP_MAX_RETRIES: 10 16 RUSTUP_MAX_RETRIES: 10
16 17
diff --git a/Cargo.lock b/Cargo.lock
index 8d81c4839..a3887ce99 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -563,9 +563,9 @@ dependencies = [
563 563
564[[package]] 564[[package]]
565name = "jod-thread" 565name = "jod-thread"
566version = "0.1.0" 566version = "0.1.1"
567source = "registry+https://github.com/rust-lang/crates.io-index" 567source = "registry+https://github.com/rust-lang/crates.io-index"
568checksum = "2f52a11f73b88fab829a0e4d9e13ea5982c7ac457c72eb3541d82a4afdfce4ff" 568checksum = "4022656272c3e564a7cdebcaaba6518d844b0d0c1836597196efb5bfeb98bb49"
569 569
570[[package]] 570[[package]]
571name = "kernel32-sys" 571name = "kernel32-sys"
@@ -893,6 +893,7 @@ dependencies = [
893 "cargo_metadata", 893 "cargo_metadata",
894 "crossbeam-channel", 894 "crossbeam-channel",
895 "insta", 895 "insta",
896 "jod-thread",
896 "log", 897 "log",
897 "lsp-types", 898 "lsp-types",
898 "serde_json", 899 "serde_json",
diff --git a/crates/ra_cargo_watch/Cargo.toml b/crates/ra_cargo_watch/Cargo.toml
index 741345a21..300033a18 100644
--- a/crates/ra_cargo_watch/Cargo.toml
+++ b/crates/ra_cargo_watch/Cargo.toml
@@ -10,6 +10,7 @@ lsp-types = { version = "0.73.0", features = ["proposed"] }
10log = "0.4.8" 10log = "0.4.8"
11cargo_metadata = "0.9.1" 11cargo_metadata = "0.9.1"
12serde_json = "1.0.48" 12serde_json = "1.0.48"
13jod-thread = "0.1.1"
13 14
14[dev-dependencies] 15[dev-dependencies]
15insta = "0.15.0" 16insta = "0.15.0"
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
index 7c525c430..c67ec39d4 100644
--- a/crates/ra_cargo_watch/src/lib.rs
+++ b/crates/ra_cargo_watch/src/lib.rs
@@ -12,7 +12,6 @@ use std::{
12 io::{BufRead, BufReader}, 12 io::{BufRead, BufReader},
13 path::{Path, PathBuf}, 13 path::{Path, PathBuf},
14 process::{Command, Stdio}, 14 process::{Command, Stdio},
15 thread::JoinHandle,
16 time::Instant, 15 time::Instant,
17}; 16};
18 17
@@ -36,9 +35,10 @@ pub struct CheckOptions {
36/// The spawned thread is shut down when this struct is dropped. 35/// The spawned thread is shut down when this struct is dropped.
37#[derive(Debug)] 36#[derive(Debug)]
38pub struct CheckWatcher { 37pub struct CheckWatcher {
38 // XXX: drop order is significant
39 cmd_send: Sender<CheckCommand>,
40 handle: Option<jod_thread::JoinHandle<()>>,
39 pub task_recv: Receiver<CheckTask>, 41 pub task_recv: Receiver<CheckTask>,
40 cmd_send: Option<Sender<CheckCommand>>,
41 handle: Option<JoinHandle<()>>,
42} 42}
43 43
44impl CheckWatcher { 44impl CheckWatcher {
@@ -47,39 +47,16 @@ impl CheckWatcher {
47 47
48 let (task_send, task_recv) = unbounded::<CheckTask>(); 48 let (task_send, task_recv) = unbounded::<CheckTask>();
49 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); 49 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
50 let handle = std::thread::spawn(move || { 50 let handle = jod_thread::spawn(move || {
51 let mut check = CheckWatcherThread::new(options, workspace_root); 51 let mut check = CheckWatcherThread::new(options, workspace_root);
52 check.run(&task_send, &cmd_recv); 52 check.run(&task_send, &cmd_recv);
53 }); 53 });
54 CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle) } 54 CheckWatcher { task_recv, cmd_send, handle: Some(handle) }
55 }
56
57 /// Returns a CheckWatcher that doesn't actually do anything
58 pub fn dummy() -> CheckWatcher {
59 CheckWatcher { task_recv: never(), cmd_send: None, handle: None }
60 } 55 }
61 56
62 /// Schedule a re-start of the cargo check worker. 57 /// Schedule a re-start of the cargo check worker.
63 pub fn update(&self) { 58 pub fn update(&self) {
64 if let Some(cmd_send) = &self.cmd_send { 59 self.cmd_send.send(CheckCommand::Update).unwrap();
65 cmd_send.send(CheckCommand::Update).unwrap();
66 }
67 }
68}
69
70impl std::ops::Drop for CheckWatcher {
71 fn drop(&mut self) {
72 if let Some(handle) = self.handle.take() {
73 // Take the sender out of the option
74 let cmd_send = self.cmd_send.take();
75
76 // Dropping the sender finishes the thread loop
77 drop(cmd_send);
78
79 // Join the thread, it should finish shortly. We don't really care
80 // whether it panicked, so it is safe to ignore the result
81 let _ = handle.join();
82 }
83 } 60 }
84} 61}
85 62
@@ -237,8 +214,9 @@ pub struct DiagnosticWithFixes {
237/// The correct way to dispose of the thread is to drop it, on which the 214/// The correct way to dispose of the thread is to drop it, on which the
238/// sub-process will be killed, and the thread will be joined. 215/// sub-process will be killed, and the thread will be joined.
239struct WatchThread { 216struct WatchThread {
240 handle: Option<JoinHandle<()>>, 217 // XXX: drop order is significant
241 message_recv: Receiver<CheckEvent>, 218 message_recv: Receiver<CheckEvent>,
219 _handle: Option<jod_thread::JoinHandle<()>>,
242} 220}
243 221
244enum CheckEvent { 222enum CheckEvent {
@@ -333,7 +311,7 @@ pub fn run_cargo(
333 311
334impl WatchThread { 312impl WatchThread {
335 fn dummy() -> WatchThread { 313 fn dummy() -> WatchThread {
336 WatchThread { handle: None, message_recv: never() } 314 WatchThread { message_recv: never(), _handle: None }
337 } 315 }
338 316
339 fn new(options: &CheckOptions, workspace_root: &Path) -> WatchThread { 317 fn new(options: &CheckOptions, workspace_root: &Path) -> WatchThread {
@@ -352,7 +330,7 @@ impl WatchThread {
352 let (message_send, message_recv) = unbounded(); 330 let (message_send, message_recv) = unbounded();
353 let workspace_root = workspace_root.to_owned(); 331 let workspace_root = workspace_root.to_owned();
354 let handle = if options.enable { 332 let handle = if options.enable {
355 Some(std::thread::spawn(move || { 333 Some(jod_thread::spawn(move || {
356 // If we trigger an error here, we will do so in the loop instead, 334 // If we trigger an error here, we will do so in the loop instead,
357 // which will break out of the loop, and continue the shutdown 335 // which will break out of the loop, and continue the shutdown
358 let _ = message_send.send(CheckEvent::Begin); 336 let _ = message_send.send(CheckEvent::Begin);
@@ -383,23 +361,6 @@ impl WatchThread {
383 } else { 361 } else {
384 None 362 None
385 }; 363 };
386 WatchThread { handle, message_recv } 364 WatchThread { message_recv, _handle: handle }
387 }
388}
389
390impl std::ops::Drop for WatchThread {
391 fn drop(&mut self) {
392 if let Some(handle) = self.handle.take() {
393 // Replace our reciever with dummy one, so we can drop and close the
394 // one actually communicating with the thread
395 let recv = std::mem::replace(&mut self.message_recv, never());
396
397 // Dropping the original reciever initiates thread sub-process shutdown
398 drop(recv);
399
400 // Join the thread, it should finish shortly. We don't really care
401 // whether it panicked, so it is safe to ignore the result
402 let _ = handle.join();
403 }
404 } 365 }
405} 366}
diff --git a/crates/ra_prof/src/lib.rs b/crates/ra_prof/src/lib.rs
index 9e167db96..00ea3a9b0 100644
--- a/crates/ra_prof/src/lib.rs
+++ b/crates/ra_prof/src/lib.rs
@@ -339,6 +339,14 @@ pub fn print_backtrace() {
339 let bt = backtrace::Backtrace::new(); 339 let bt = backtrace::Backtrace::new();
340 eprintln!("{:?}", bt); 340 eprintln!("{:?}", bt);
341} 341}
342#[cfg(not(feature = "backtrace"))]
343pub fn print_backtrace() {
344 eprintln!(
345 r#"enable the backtrace feature:
346 ra_prof = {{ path = "../ra_prof", features = [ "backtrace"] }}
347"#
348 );
349}
342 350
343thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false)); 351thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false));
344 352
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 7825b0077..c899ff677 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -14,7 +14,7 @@ use std::{
14 time::{Duration, Instant}, 14 time::{Duration, Instant},
15}; 15};
16 16
17use crossbeam_channel::{select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
19use lsp_types::{ 19use lsp_types::{
20 ClientCapabilities, NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, 20 ClientCapabilities, NumberOrString, WorkDoneProgress, WorkDoneProgressBegin,
@@ -208,6 +208,9 @@ pub fn main_loop(
208 ) 208 )
209 }; 209 };
210 210
211 loop_state.roots_total = world_state.vfs.read().n_roots();
212 loop_state.roots_scanned = 0;
213
211 let pool = ThreadPool::default(); 214 let pool = ThreadPool::default();
212 let (task_sender, task_receiver) = unbounded::<Task>(); 215 let (task_sender, task_receiver) = unbounded::<Task>();
213 let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); 216 let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>();
@@ -229,7 +232,7 @@ pub fn main_loop(
229 Err(RecvError) => return Err("vfs died".into()), 232 Err(RecvError) => return Err("vfs died".into()),
230 }, 233 },
231 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), 234 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()),
232 recv(world_state.check_watcher.task_recv) -> task => match task { 235 recv(world_state.check_watcher.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task {
233 Ok(task) => Event::CheckWatcher(task), 236 Ok(task) => Event::CheckWatcher(task),
234 Err(RecvError) => return Err("check watcher died".into()), 237 Err(RecvError) => return Err("check watcher died".into()),
235 } 238 }
@@ -333,7 +336,10 @@ struct LoopState {
333 in_flight_libraries: usize, 336 in_flight_libraries: usize,
334 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>, 337 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>,
335 workspace_loaded: bool, 338 workspace_loaded: bool,
336 roots_scanned_progress: Option<usize>, 339
340 roots_progress_reported: Option<usize>,
341 roots_scanned: usize,
342 roots_total: usize,
337} 343}
338 344
339impl LoopState { 345impl LoopState {
@@ -377,6 +383,7 @@ fn loop_turn(
377 world_state.add_lib(lib); 383 world_state.add_lib(lib);
378 world_state.maybe_collect_garbage(); 384 world_state.maybe_collect_garbage();
379 loop_state.in_flight_libraries -= 1; 385 loop_state.in_flight_libraries -= 1;
386 loop_state.roots_scanned += 1;
380 } 387 }
381 Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, 388 Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?,
382 Event::Msg(msg) => match msg { 389 Event::Msg(msg) => match msg {
@@ -408,7 +415,7 @@ fn loop_turn(
408 }; 415 };
409 416
410 let mut state_changed = false; 417 let mut state_changed = false;
411 if let Some(changes) = world_state.process_changes() { 418 if let Some(changes) = world_state.process_changes(&mut loop_state.roots_scanned) {
412 state_changed = true; 419 state_changed = true;
413 loop_state.pending_libraries.extend(changes); 420 loop_state.pending_libraries.extend(changes);
414 } 421 }
@@ -427,21 +434,27 @@ fn loop_turn(
427 }); 434 });
428 } 435 }
429 436
437 let show_progress = !loop_state.workspace_loaded
438 && world_state.feature_flags.get("notifications.workspace-loaded");
439
430 if !loop_state.workspace_loaded 440 if !loop_state.workspace_loaded
431 && world_state.roots_to_scan == 0 441 && loop_state.roots_scanned == loop_state.roots_total
432 && loop_state.pending_libraries.is_empty() 442 && loop_state.pending_libraries.is_empty()
433 && loop_state.in_flight_libraries == 0 443 && loop_state.in_flight_libraries == 0
434 { 444 {
435 loop_state.workspace_loaded = true; 445 loop_state.workspace_loaded = true;
436 world_state.check_watcher.update(); 446 if let Some(check_watcher) = &world_state.check_watcher {
447 check_watcher.update();
448 }
437 pool.execute({ 449 pool.execute({
438 let subs = loop_state.subscriptions.subscriptions(); 450 let subs = loop_state.subscriptions.subscriptions();
439 let snap = world_state.snapshot(); 451 let snap = world_state.snapshot();
440 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) 452 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
441 }); 453 });
442 send_startup_progress(&connection.sender, loop_state, world_state); 454 }
443 } else if !loop_state.workspace_loaded { 455
444 send_startup_progress(&connection.sender, loop_state, world_state); 456 if show_progress {
457 send_startup_progress(&connection.sender, loop_state);
445 } 458 }
446 459
447 if state_changed { 460 if state_changed {
@@ -604,7 +617,9 @@ fn on_notification(
604 }; 617 };
605 let not = match notification_cast::<req::DidSaveTextDocument>(not) { 618 let not = match notification_cast::<req::DidSaveTextDocument>(not) {
606 Ok(_params) => { 619 Ok(_params) => {
607 state.check_watcher.update(); 620 if let Some(check_watcher) = &state.check_watcher {
621 check_watcher.update();
622 }
608 return Ok(()); 623 return Ok(());
609 } 624 }
610 Err(not) => not, 625 Err(not) => not,
@@ -706,21 +721,13 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
706 } 721 }
707} 722}
708 723
709fn send_startup_progress( 724fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) {
710 sender: &Sender<Message>, 725 let total: usize = loop_state.roots_total;
711 loop_state: &mut LoopState, 726 let prev = loop_state.roots_progress_reported;
712 world_state: &WorldState, 727 let progress = loop_state.roots_scanned;
713) { 728 loop_state.roots_progress_reported = Some(progress);
714 if !world_state.feature_flags.get("notifications.workspace-loaded") {
715 return;
716 }
717
718 let total: usize = world_state.workspaces.iter().map(|it| it.n_packages()).sum();
719 let prev_progress = loop_state.roots_scanned_progress;
720 let progress = total - world_state.roots_to_scan;
721 loop_state.roots_scanned_progress = Some(progress);
722 729
723 match (prev_progress, loop_state.workspace_loaded) { 730 match (prev, loop_state.workspace_loaded) {
724 (None, false) => { 731 (None, false) => {
725 let work_done_progress_create = request_new::<req::WorkDoneProgressCreate>( 732 let work_done_progress_create = request_new::<req::WorkDoneProgressCreate>(
726 loop_state.next_request_id(), 733 loop_state.next_request_id(),
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index 64a7b907e..ca045f93c 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -51,15 +51,13 @@ pub struct Options {
51pub struct WorldState { 51pub struct WorldState {
52 pub options: Options, 52 pub options: Options,
53 pub feature_flags: Arc<FeatureFlags>, 53 pub feature_flags: Arc<FeatureFlags>,
54 //FIXME: this belongs to `LoopState` rather than to `WorldState`
55 pub roots_to_scan: usize,
56 pub roots: Vec<PathBuf>, 54 pub roots: Vec<PathBuf>,
57 pub workspaces: Arc<Vec<ProjectWorkspace>>, 55 pub workspaces: Arc<Vec<ProjectWorkspace>>,
58 pub analysis_host: AnalysisHost, 56 pub analysis_host: AnalysisHost,
59 pub vfs: Arc<RwLock<Vfs>>, 57 pub vfs: Arc<RwLock<Vfs>>,
60 pub task_receiver: Receiver<VfsTask>, 58 pub task_receiver: Receiver<VfsTask>,
61 pub latest_requests: Arc<RwLock<LatestRequests>>, 59 pub latest_requests: Arc<RwLock<LatestRequests>>,
62 pub check_watcher: CheckWatcher, 60 pub check_watcher: Option<CheckWatcher>,
63 pub diagnostics: DiagnosticCollection, 61 pub diagnostics: DiagnosticCollection,
64} 62}
65 63
@@ -123,7 +121,7 @@ impl WorldState {
123 let (task_sender, task_receiver) = unbounded(); 121 let (task_sender, task_receiver) = unbounded();
124 let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); 122 let task_sender = Box::new(move |t| task_sender.send(t).unwrap());
125 let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); 123 let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch);
126 let roots_to_scan = vfs_roots.len(); 124
127 for r in vfs_roots { 125 for r in vfs_roots {
128 let vfs_root_path = vfs.root2path(r); 126 let vfs_root_path = vfs.root2path(r);
129 let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it)); 127 let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it));
@@ -178,11 +176,11 @@ impl WorldState {
178 }) 176 })
179 .map(|cargo| { 177 .map(|cargo| {
180 let cargo_project_root = cargo.workspace_root().to_path_buf(); 178 let cargo_project_root = cargo.workspace_root().to_path_buf();
181 CheckWatcher::new(&options.cargo_watch, cargo_project_root) 179 Some(CheckWatcher::new(&options.cargo_watch, cargo_project_root))
182 }) 180 })
183 .unwrap_or_else(|| { 181 .unwrap_or_else(|| {
184 log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); 182 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
185 CheckWatcher::dummy() 183 None
186 }); 184 });
187 185
188 let mut analysis_host = AnalysisHost::new(lru_capacity); 186 let mut analysis_host = AnalysisHost::new(lru_capacity);
@@ -190,7 +188,6 @@ impl WorldState {
190 WorldState { 188 WorldState {
191 options, 189 options,
192 feature_flags: Arc::new(feature_flags), 190 feature_flags: Arc::new(feature_flags),
193 roots_to_scan,
194 roots: folder_roots, 191 roots: folder_roots,
195 workspaces: Arc::new(workspaces), 192 workspaces: Arc::new(workspaces),
196 analysis_host, 193 analysis_host,
@@ -206,6 +203,7 @@ impl WorldState {
206 /// FIXME: better API here 203 /// FIXME: better API here
207 pub fn process_changes( 204 pub fn process_changes(
208 &mut self, 205 &mut self,
206 roots_scanned: &mut usize,
209 ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> { 207 ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> {
210 let changes = self.vfs.write().commit_changes(); 208 let changes = self.vfs.write().commit_changes();
211 if changes.is_empty() { 209 if changes.is_empty() {
@@ -219,7 +217,7 @@ impl WorldState {
219 let root_path = self.vfs.read().root2path(root); 217 let root_path = self.vfs.read().root2path(root);
220 let is_local = self.roots.iter().any(|r| root_path.starts_with(r)); 218 let is_local = self.roots.iter().any(|r| root_path.starts_with(r));
221 if is_local { 219 if is_local {
222 self.roots_to_scan -= 1; 220 *roots_scanned += 1;
223 for (file, path, text) in files { 221 for (file, path, text) in files {
224 change.add_file(SourceRootId(root.0), FileId(file.0), path, text); 222 change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
225 } 223 }
@@ -247,7 +245,6 @@ impl WorldState {
247 } 245 }
248 246
249 pub fn add_lib(&mut self, data: LibraryData) { 247 pub fn add_lib(&mut self, data: LibraryData) {
250 self.roots_to_scan -= 1;
251 let mut change = AnalysisChange::new(); 248 let mut change = AnalysisChange::new();
252 change.add_library(data); 249 change.add_library(data);
253 self.analysis_host.apply_change(change); 250 self.analysis_host.apply_change(change);
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index 1d7062bdf..67f3c9332 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -83,9 +83,10 @@ pub fn project(fixture: &str) -> Server {
83pub struct Server { 83pub struct Server {
84 req_id: Cell<u64>, 84 req_id: Cell<u64>,
85 messages: RefCell<Vec<Message>>, 85 messages: RefCell<Vec<Message>>,
86 dir: TempDir,
87 _thread: jod_thread::JoinHandle<()>, 86 _thread: jod_thread::JoinHandle<()>,
88 client: Connection, 87 client: Connection,
88 /// XXX: remove the tempdir last
89 dir: TempDir,
89} 90}
90 91
91impl Server { 92impl Server {