diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-02-03 11:27:31 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-02-03 11:27:31 +0000 |
commit | d400fde66c7139aad04e5c5c9875a1d41ef5eae8 (patch) | |
tree | f86b6711bc6fb006d99b0be09bf17f0a9a79b3ec /crates/ra_lsp_server | |
parent | 5b1b2cac39d88e730d594f951ab6613eab5ee498 (diff) | |
parent | 9f70f443a35bc12caf37e2548abf7987926c3875 (diff) |
Merge #2959
2959: Rework how we send diagnostics to client r=matklad a=kiljacken
The previous way of sending from the thread pool suffered from stale diagnostics due to being canceled before we could clear the old ones.
The key change is moving to sending diagnostics from the main loop thread, but doing all the hard work in the thread pool. This should provide the best of both worlds, with little to no of the downsides.
This should hopefully fix a lot of issues, but we'll need testing in each individual issue to be sure.
Co-authored-by: Emil Lauridsen <[email protected]>
Diffstat (limited to 'crates/ra_lsp_server')
-rw-r--r-- | crates/ra_lsp_server/src/diagnostics.rs | 85 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop.rs | 76 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 36 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/world.rs | 11 |
5 files changed, 139 insertions, 70 deletions
diff --git a/crates/ra_lsp_server/src/diagnostics.rs b/crates/ra_lsp_server/src/diagnostics.rs new file mode 100644 index 000000000..ea08bce24 --- /dev/null +++ b/crates/ra_lsp_server/src/diagnostics.rs | |||
@@ -0,0 +1,85 @@ | |||
1 | //! Book keeping for keeping diagnostics easily in sync with the client. | ||
2 | use lsp_types::{CodeActionOrCommand, Diagnostic, Range}; | ||
3 | use ra_ide::FileId; | ||
4 | use std::{collections::HashMap, sync::Arc}; | ||
5 | |||
6 | pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>; | ||
7 | |||
8 | #[derive(Debug, Default, Clone)] | ||
9 | pub struct DiagnosticCollection { | ||
10 | pub native: HashMap<FileId, Vec<Diagnostic>>, | ||
11 | pub check: HashMap<FileId, Vec<Diagnostic>>, | ||
12 | pub check_fixes: CheckFixes, | ||
13 | } | ||
14 | |||
15 | #[derive(Debug, Clone)] | ||
16 | pub struct Fix { | ||
17 | pub range: Range, | ||
18 | pub action: CodeActionOrCommand, | ||
19 | } | ||
20 | |||
21 | #[derive(Debug)] | ||
22 | pub enum DiagnosticTask { | ||
23 | ClearCheck, | ||
24 | AddCheck(FileId, Diagnostic, Vec<CodeActionOrCommand>), | ||
25 | SetNative(FileId, Vec<Diagnostic>), | ||
26 | } | ||
27 | |||
28 | impl DiagnosticCollection { | ||
29 | pub fn clear_check(&mut self) -> Vec<FileId> { | ||
30 | Arc::make_mut(&mut self.check_fixes).clear(); | ||
31 | self.check.drain().map(|(key, _value)| key).collect() | ||
32 | } | ||
33 | |||
34 | pub fn add_check_diagnostic( | ||
35 | &mut self, | ||
36 | file_id: FileId, | ||
37 | diagnostic: Diagnostic, | ||
38 | fixes: Vec<CodeActionOrCommand>, | ||
39 | ) { | ||
40 | let diagnostics = self.check.entry(file_id).or_default(); | ||
41 | for existing_diagnostic in diagnostics.iter() { | ||
42 | if are_diagnostics_equal(&existing_diagnostic, &diagnostic) { | ||
43 | return; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | let check_fixes = Arc::make_mut(&mut self.check_fixes); | ||
48 | check_fixes | ||
49 | .entry(file_id) | ||
50 | .or_default() | ||
51 | .extend(fixes.into_iter().map(|action| Fix { range: diagnostic.range, action })); | ||
52 | diagnostics.push(diagnostic); | ||
53 | } | ||
54 | |||
55 | pub fn set_native_diagnostics(&mut self, file_id: FileId, diagnostics: Vec<Diagnostic>) { | ||
56 | self.native.insert(file_id, diagnostics); | ||
57 | } | ||
58 | |||
59 | pub fn diagnostics_for(&self, file_id: FileId) -> impl Iterator<Item = &Diagnostic> { | ||
60 | let native = self.native.get(&file_id).into_iter().flatten(); | ||
61 | let check = self.check.get(&file_id).into_iter().flatten(); | ||
62 | native.chain(check) | ||
63 | } | ||
64 | |||
65 | pub fn handle_task(&mut self, task: DiagnosticTask) -> Vec<FileId> { | ||
66 | match task { | ||
67 | DiagnosticTask::ClearCheck => self.clear_check(), | ||
68 | DiagnosticTask::AddCheck(file_id, diagnostic, fixes) => { | ||
69 | self.add_check_diagnostic(file_id, diagnostic, fixes); | ||
70 | vec![file_id] | ||
71 | } | ||
72 | DiagnosticTask::SetNative(file_id, diagnostics) => { | ||
73 | self.set_native_diagnostics(file_id, diagnostics); | ||
74 | vec![file_id] | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | } | ||
79 | |||
80 | fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool { | ||
81 | left.source == right.source | ||
82 | && left.severity == right.severity | ||
83 | && left.range == right.range | ||
84 | && left.message == right.message | ||
85 | } | ||
diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index 2ca149fd5..1208c1343 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs | |||
@@ -29,6 +29,7 @@ mod markdown; | |||
29 | pub mod req; | 29 | pub mod req; |
30 | mod config; | 30 | mod config; |
31 | mod world; | 31 | mod world; |
32 | mod diagnostics; | ||
32 | 33 | ||
33 | pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | 34 | pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; |
34 | pub use crate::{ | 35 | pub use crate::{ |
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 508fe08c0..12961ba37 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs | |||
@@ -17,16 +17,17 @@ use std::{ | |||
17 | use crossbeam_channel::{select, unbounded, RecvError, Sender}; | 17 | use crossbeam_channel::{select, unbounded, RecvError, Sender}; |
18 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; | 18 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; |
19 | use lsp_types::{ClientCapabilities, NumberOrString}; | 19 | use lsp_types::{ClientCapabilities, NumberOrString}; |
20 | use ra_cargo_watch::{CheckOptions, CheckTask}; | 20 | use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask}; |
21 | use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; | 21 | use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; |
22 | use ra_prof::profile; | 22 | use ra_prof::profile; |
23 | use ra_vfs::{VfsTask, Watch}; | 23 | use ra_vfs::{VfsFile, VfsTask, Watch}; |
24 | use relative_path::RelativePathBuf; | 24 | use relative_path::RelativePathBuf; |
25 | use rustc_hash::FxHashSet; | 25 | use rustc_hash::FxHashSet; |
26 | use serde::{de::DeserializeOwned, Serialize}; | 26 | use serde::{de::DeserializeOwned, Serialize}; |
27 | use threadpool::ThreadPool; | 27 | use threadpool::ThreadPool; |
28 | 28 | ||
29 | use crate::{ | 29 | use crate::{ |
30 | diagnostics::DiagnosticTask, | ||
30 | main_loop::{ | 31 | main_loop::{ |
31 | pending_requests::{PendingRequest, PendingRequests}, | 32 | pending_requests::{PendingRequest, PendingRequests}, |
32 | subscriptions::Subscriptions, | 33 | subscriptions::Subscriptions, |
@@ -254,6 +255,7 @@ pub fn main_loop( | |||
254 | enum Task { | 255 | enum Task { |
255 | Respond(Response), | 256 | Respond(Response), |
256 | Notify(Notification), | 257 | Notify(Notification), |
258 | Diagnostic(DiagnosticTask), | ||
257 | } | 259 | } |
258 | 260 | ||
259 | enum Event { | 261 | enum Event { |
@@ -359,7 +361,7 @@ fn loop_turn( | |||
359 | world_state.maybe_collect_garbage(); | 361 | world_state.maybe_collect_garbage(); |
360 | loop_state.in_flight_libraries -= 1; | 362 | loop_state.in_flight_libraries -= 1; |
361 | } | 363 | } |
362 | Event::CheckWatcher(task) => on_check_task(pool, task, world_state, task_sender)?, | 364 | Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, |
363 | Event::Msg(msg) => match msg { | 365 | Event::Msg(msg) => match msg { |
364 | Message::Request(req) => on_request( | 366 | Message::Request(req) => on_request( |
365 | world_state, | 367 | world_state, |
@@ -464,6 +466,7 @@ fn on_task( | |||
464 | Task::Notify(n) => { | 466 | Task::Notify(n) => { |
465 | msg_sender.send(n.into()).unwrap(); | 467 | msg_sender.send(n.into()).unwrap(); |
466 | } | 468 | } |
469 | Task::Diagnostic(task) => on_diagnostic_task(task, msg_sender, state), | ||
467 | } | 470 | } |
468 | } | 471 | } |
469 | 472 | ||
@@ -621,23 +624,26 @@ fn on_notification( | |||
621 | } | 624 | } |
622 | 625 | ||
623 | fn on_check_task( | 626 | fn on_check_task( |
624 | pool: &ThreadPool, | ||
625 | task: CheckTask, | 627 | task: CheckTask, |
626 | world_state: &mut WorldState, | 628 | world_state: &mut WorldState, |
627 | task_sender: &Sender<Task>, | 629 | task_sender: &Sender<Task>, |
628 | ) -> Result<()> { | 630 | ) -> Result<()> { |
629 | let urls = match task { | 631 | match task { |
630 | CheckTask::ClearDiagnostics => { | 632 | CheckTask::ClearDiagnostics => { |
631 | let state = Arc::get_mut(&mut world_state.check_watcher.state) | 633 | task_sender.send(Task::Diagnostic(DiagnosticTask::ClearCheck))?; |
632 | .expect("couldn't get check watcher state as mutable"); | ||
633 | state.clear() | ||
634 | } | 634 | } |
635 | 635 | ||
636 | CheckTask::AddDiagnostic(url, diagnostic) => { | 636 | CheckTask::AddDiagnostic { url, diagnostic, fixes } => { |
637 | let state = Arc::get_mut(&mut world_state.check_watcher.state) | 637 | let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; |
638 | .expect("couldn't get check watcher state as mutable"); | 638 | let file_id = world_state |
639 | state.add_diagnostic_with_fixes(url.clone(), diagnostic); | 639 | .vfs |
640 | vec![url] | 640 | .read() |
641 | .path2file(&path) | ||
642 | .map(|it| FileId(it.0)) | ||
643 | .ok_or_else(|| format!("unknown file: {}", path.to_string_lossy()))?; | ||
644 | |||
645 | task_sender | ||
646 | .send(Task::Diagnostic(DiagnosticTask::AddCheck(file_id, diagnostic, fixes)))?; | ||
641 | } | 647 | } |
642 | 648 | ||
643 | CheckTask::Status(progress) => { | 649 | CheckTask::Status(progress) => { |
@@ -647,31 +653,30 @@ fn on_check_task( | |||
647 | }; | 653 | }; |
648 | let not = notification_new::<req::Progress>(params); | 654 | let not = notification_new::<req::Progress>(params); |
649 | task_sender.send(Task::Notify(not)).unwrap(); | 655 | task_sender.send(Task::Notify(not)).unwrap(); |
650 | Vec::new() | ||
651 | } | 656 | } |
652 | }; | 657 | }; |
653 | 658 | ||
654 | let subscriptions = urls | 659 | Ok(()) |
655 | .into_iter() | 660 | } |
656 | .map(|url| { | ||
657 | let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; | ||
658 | Ok(world_state.vfs.read().path2file(&path).map(|it| FileId(it.0))) | ||
659 | }) | ||
660 | .filter_map(|res| res.transpose()) | ||
661 | .collect::<Result<Vec<_>>>()?; | ||
662 | 661 | ||
663 | // We manually send a diagnostic update when the watcher asks | 662 | fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: &mut WorldState) { |
664 | // us to, to avoid the issue of having to change the file to | 663 | let subscriptions = state.diagnostics.handle_task(task); |
665 | // receive updated diagnostics. | ||
666 | update_file_notifications_on_threadpool( | ||
667 | pool, | ||
668 | world_state.snapshot(), | ||
669 | false, | ||
670 | task_sender.clone(), | ||
671 | subscriptions, | ||
672 | ); | ||
673 | 664 | ||
674 | Ok(()) | 665 | for file_id in subscriptions { |
666 | let path = state.vfs.read().file2path(VfsFile(file_id.0)); | ||
667 | let uri = match url_from_path_with_drive_lowercasing(&path) { | ||
668 | Ok(uri) => uri, | ||
669 | Err(err) => { | ||
670 | log::error!("Couldn't convert path to url ({}): {:?}", err, path.to_string_lossy()); | ||
671 | continue; | ||
672 | } | ||
673 | }; | ||
674 | |||
675 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); | ||
676 | let params = req::PublishDiagnosticsParams { uri, diagnostics, version: None }; | ||
677 | let not = notification_new::<req::PublishDiagnostics>(params); | ||
678 | msg_sender.send(not.into()).unwrap(); | ||
679 | } | ||
675 | } | 680 | } |
676 | 681 | ||
677 | struct PoolDispatcher<'a> { | 682 | struct PoolDispatcher<'a> { |
@@ -819,9 +824,8 @@ fn update_file_notifications_on_threadpool( | |||
819 | log::error!("failed to compute diagnostics: {:?}", e); | 824 | log::error!("failed to compute diagnostics: {:?}", e); |
820 | } | 825 | } |
821 | } | 826 | } |
822 | Ok(params) => { | 827 | Ok(task) => { |
823 | let not = notification_new::<req::PublishDiagnostics>(params); | 828 | task_sender.send(Task::Diagnostic(task)).unwrap(); |
824 | task_sender.send(Task::Notify(not)).unwrap(); | ||
825 | } | 829 | } |
826 | } | 830 | } |
827 | } | 831 | } |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 9aa1e7eea..282f6e8fc 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -33,6 +33,7 @@ use crate::{ | |||
33 | to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, | 33 | to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, |
34 | TryConvWithToVec, | 34 | TryConvWithToVec, |
35 | }, | 35 | }, |
36 | diagnostics::DiagnosticTask, | ||
36 | req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, | 37 | req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, |
37 | world::WorldSnapshot, | 38 | world::WorldSnapshot, |
38 | LspError, Result, | 39 | LspError, Result, |
@@ -676,28 +677,12 @@ pub fn handle_code_action( | |||
676 | res.push(action.into()); | 677 | res.push(action.into()); |
677 | } | 678 | } |
678 | 679 | ||
679 | for fix in world.check_watcher.fixes_for(¶ms.text_document.uri).into_iter().flatten() { | 680 | for fix in world.check_fixes.get(&file_id).into_iter().flatten() { |
680 | let fix_range = fix.location.range.conv_with(&line_index); | 681 | let fix_range = fix.range.conv_with(&line_index); |
681 | if fix_range.intersection(&range).is_none() { | 682 | if fix_range.intersection(&range).is_none() { |
682 | continue; | 683 | continue; |
683 | } | 684 | } |
684 | 685 | res.push(fix.action.clone()); | |
685 | let edit = { | ||
686 | let edits = vec![TextEdit::new(fix.location.range, fix.replacement.clone())]; | ||
687 | let mut edit_map = std::collections::HashMap::new(); | ||
688 | edit_map.insert(fix.location.uri.clone(), edits); | ||
689 | WorkspaceEdit::new(edit_map) | ||
690 | }; | ||
691 | |||
692 | let action = CodeAction { | ||
693 | title: fix.title.clone(), | ||
694 | kind: Some("quickfix".to_string()), | ||
695 | diagnostics: Some(fix.diagnostics.clone()), | ||
696 | edit: Some(edit), | ||
697 | command: None, | ||
698 | is_preferred: None, | ||
699 | }; | ||
700 | res.push(action.into()); | ||
701 | } | 686 | } |
702 | 687 | ||
703 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { | 688 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { |
@@ -875,14 +860,10 @@ pub fn handle_document_highlight( | |||
875 | )) | 860 | )) |
876 | } | 861 | } |
877 | 862 | ||
878 | pub fn publish_diagnostics( | 863 | pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> { |
879 | world: &WorldSnapshot, | ||
880 | file_id: FileId, | ||
881 | ) -> Result<req::PublishDiagnosticsParams> { | ||
882 | let _p = profile("publish_diagnostics"); | 864 | let _p = profile("publish_diagnostics"); |
883 | let uri = world.file_id_to_uri(file_id)?; | ||
884 | let line_index = world.analysis().file_line_index(file_id)?; | 865 | let line_index = world.analysis().file_line_index(file_id)?; |
885 | let mut diagnostics: Vec<Diagnostic> = world | 866 | let diagnostics: Vec<Diagnostic> = world |
886 | .analysis() | 867 | .analysis() |
887 | .diagnostics(file_id)? | 868 | .diagnostics(file_id)? |
888 | .into_iter() | 869 | .into_iter() |
@@ -896,10 +877,7 @@ pub fn publish_diagnostics( | |||
896 | tags: None, | 877 | tags: None, |
897 | }) | 878 | }) |
898 | .collect(); | 879 | .collect(); |
899 | if let Some(check_diags) = world.check_watcher.diagnostics_for(&uri) { | 880 | Ok(DiagnosticTask::SetNative(file_id, diagnostics)) |
900 | diagnostics.extend(check_diags.iter().cloned()); | ||
901 | } | ||
902 | Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None }) | ||
903 | } | 881 | } |
904 | 882 | ||
905 | pub fn publish_decorations( | 883 | pub fn publish_decorations( |
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index 3059ef9ec..1ee02b47c 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs | |||
@@ -12,9 +12,7 @@ use crossbeam_channel::{unbounded, Receiver}; | |||
12 | use lsp_server::ErrorCode; | 12 | use lsp_server::ErrorCode; |
13 | use lsp_types::Url; | 13 | use lsp_types::Url; |
14 | use parking_lot::RwLock; | 14 | use parking_lot::RwLock; |
15 | use ra_cargo_watch::{ | 15 | use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckWatcher}; |
16 | url_from_path_with_drive_lowercasing, CheckOptions, CheckState, CheckWatcher, | ||
17 | }; | ||
18 | use ra_ide::{ | 16 | use ra_ide::{ |
19 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, | 17 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, |
20 | SourceRootId, | 18 | SourceRootId, |
@@ -25,6 +23,7 @@ use ra_vfs_glob::{Glob, RustPackageFilterBuilder}; | |||
25 | use relative_path::RelativePathBuf; | 23 | use relative_path::RelativePathBuf; |
26 | 24 | ||
27 | use crate::{ | 25 | use crate::{ |
26 | diagnostics::{CheckFixes, DiagnosticCollection}, | ||
28 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, | 27 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, |
29 | LspError, Result, | 28 | LspError, Result, |
30 | }; | 29 | }; |
@@ -55,6 +54,7 @@ pub struct WorldState { | |||
55 | pub task_receiver: Receiver<VfsTask>, | 54 | pub task_receiver: Receiver<VfsTask>, |
56 | pub latest_requests: Arc<RwLock<LatestRequests>>, | 55 | pub latest_requests: Arc<RwLock<LatestRequests>>, |
57 | pub check_watcher: CheckWatcher, | 56 | pub check_watcher: CheckWatcher, |
57 | pub diagnostics: DiagnosticCollection, | ||
58 | } | 58 | } |
59 | 59 | ||
60 | /// An immutable snapshot of the world's state at a point in time. | 60 | /// An immutable snapshot of the world's state at a point in time. |
@@ -63,7 +63,7 @@ pub struct WorldSnapshot { | |||
63 | pub workspaces: Arc<Vec<ProjectWorkspace>>, | 63 | pub workspaces: Arc<Vec<ProjectWorkspace>>, |
64 | pub analysis: Analysis, | 64 | pub analysis: Analysis, |
65 | pub latest_requests: Arc<RwLock<LatestRequests>>, | 65 | pub latest_requests: Arc<RwLock<LatestRequests>>, |
66 | pub check_watcher: CheckState, | 66 | pub check_fixes: CheckFixes, |
67 | vfs: Arc<RwLock<Vfs>>, | 67 | vfs: Arc<RwLock<Vfs>>, |
68 | } | 68 | } |
69 | 69 | ||
@@ -159,6 +159,7 @@ impl WorldState { | |||
159 | task_receiver, | 159 | task_receiver, |
160 | latest_requests: Default::default(), | 160 | latest_requests: Default::default(), |
161 | check_watcher, | 161 | check_watcher, |
162 | diagnostics: Default::default(), | ||
162 | } | 163 | } |
163 | } | 164 | } |
164 | 165 | ||
@@ -220,7 +221,7 @@ impl WorldState { | |||
220 | analysis: self.analysis_host.analysis(), | 221 | analysis: self.analysis_host.analysis(), |
221 | vfs: Arc::clone(&self.vfs), | 222 | vfs: Arc::clone(&self.vfs), |
222 | latest_requests: Arc::clone(&self.latest_requests), | 223 | latest_requests: Arc::clone(&self.latest_requests), |
223 | check_watcher: (*self.check_watcher.state).clone(), | 224 | check_fixes: Arc::clone(&self.diagnostics.check_fixes), |
224 | } | 225 | } |
225 | } | 226 | } |
226 | 227 | ||