Skip to content

Commit 3bf4917

Browse files
committed
address review comments and ensure lock is always aquired
1 parent 73b7bed commit 3bf4917

File tree

4 files changed

+96
-74
lines changed

4 files changed

+96
-74
lines changed

helix-vcs/src/diff.rs

+39-27
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,33 @@ use helix_core::Rope;
55
use imara_diff::Algorithm;
66
use parking_lot::{Mutex, MutexGuard};
77
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
8-
use tokio::sync::{Notify, RwLock};
8+
use tokio::sync::{Notify, OwnedRwLockReadGuard, RwLock};
99
use tokio::task::JoinHandle;
10+
use tokio::time::Instant;
1011

1112
use crate::diff::worker::DiffWorker;
1213

1314
mod line_cache;
1415
mod worker;
1516

16-
type RedrawHandle = Arc<(Notify, RwLock<()>)>;
17+
type RedrawHandle = (Arc<Notify>, Arc<RwLock<()>>);
1718

18-
// The order of enum variants is used by the PartialOrd
19-
// derive macro, DO NOT REORDER
20-
#[derive(PartialEq, PartialOrd)]
21-
enum RenderStrategy {
22-
Async,
23-
SyncWithTimeout,
24-
Sync,
19+
/// A rendering lock passed to the differ the prevents redraws from occuring
20+
struct RenderLock {
21+
pub lock: OwnedRwLockReadGuard<()>,
22+
pub timeout: Option<Instant>,
2523
}
2624

2725
struct Event {
2826
text: Rope,
2927
is_base: bool,
30-
render_strategy: RenderStrategy,
28+
render_lock: Option<RenderLock>,
3129
}
3230

3331
#[derive(Clone, Debug)]
3432
pub struct DiffHandle {
3533
channel: UnboundedSender<Event>,
34+
render_lock: Arc<RwLock<()>>,
3635
hunks: Arc<Mutex<Vec<Hunk>>>,
3736
inverted: bool,
3837
}
@@ -53,14 +52,15 @@ impl DiffHandle {
5352
channel: receiver,
5453
hunks: hunks.clone(),
5554
new_hunks: Vec::default(),
56-
redraw_handle,
55+
redraw_notify: redraw_handle.0,
5756
difff_finished_notify: Arc::default(),
5857
};
5958
let handle = tokio::spawn(worker.run(diff_base, doc));
6059
let differ = DiffHandle {
6160
channel: sender,
6261
hunks,
6362
inverted: false,
63+
render_lock: redraw_handle.1,
6464
};
6565
(differ, handle)
6666
}
@@ -76,42 +76,54 @@ impl DiffHandle {
7676
}
7777
}
7878

79+
/// Updates the document associated with this redraw handle
80+
/// This function is only intended to be called from within the rendering loop
81+
/// if called from elsewhere it may fail to aquire the render lock and panic
7982
pub fn update_document(&self, doc: Rope, block: bool) -> bool {
80-
let mode = if block {
81-
RenderStrategy::Sync
83+
// unwrap is ok here because the rendering lock is
84+
// only exclusively locked during redraw.
85+
// This function is only intended to be called
86+
// from the core rendering loop where no redraw can happen in parallel
87+
let lock = self.render_lock.clone().try_read_owned().unwrap();
88+
let timeout = if block {
89+
None
8290
} else {
83-
RenderStrategy::SyncWithTimeout
91+
Some(Instant::now() + tokio::time::Duration::from_millis(SYNC_DIFF_TIMEOUT))
8492
};
85-
self.update_document_impl(doc, self.inverted, mode)
93+
self.update_document_impl(doc, self.inverted, Some(RenderLock { lock, timeout }))
8694
}
8795

8896
pub fn update_diff_base(&self, diff_base: Rope) -> bool {
89-
self.update_document_impl(diff_base, !self.inverted, RenderStrategy::Async)
97+
self.update_document_impl(diff_base, !self.inverted, None)
9098
}
9199

92-
fn update_document_impl(&self, text: Rope, is_base: bool, mode: RenderStrategy) -> bool {
100+
fn update_document_impl(
101+
&self,
102+
text: Rope,
103+
is_base: bool,
104+
render_lock: Option<RenderLock>,
105+
) -> bool {
93106
let event = Event {
94107
text,
95108
is_base,
96-
render_strategy: mode,
109+
render_lock,
97110
};
98111
self.channel.send(event).is_ok()
99112
}
100113
}
101114

102-
// TODO configuration
103-
/// synchronous debounce value should be low
104-
/// so we can update synchronously most of the time
115+
/// synchronus debounce value should be low
116+
/// so we can update synchrously most of the time
105117
const DIFF_DEBOUNCE_TIME_SYNC: u64 = 1;
106118
/// maximum time that rendering should be blocked until the diff finishes
107-
const SYNC_DIFF_TIMEOUT: u64 = 50;
108-
const DIFF_DEBOUNCE_TIME_ASYNC: u64 = 100;
119+
const SYNC_DIFF_TIMEOUT: u64 = 12;
120+
const DIFF_DEBOUNCE_TIME_ASYNC: u64 = 96;
109121
const ALGORITHM: Algorithm = Algorithm::Histogram;
110122
const MAX_DIFF_LINES: usize = 64 * u16::MAX as usize;
111123
// cap average line length to 128 for files with MAX_DIFF_LINES
112124
const MAX_DIFF_BYTES: usize = MAX_DIFF_LINES * 128;
113125

114-
/// A single change in a file potentially spanning multiple lines
126+
/// A single change in a file potentially sppaning multiple lines
115127
/// Hunks produced by the differs are always ordered by their position
116128
/// in the file and non-overlapping.
117129
/// Specifically for any two hunks `x` and `y` the following properties hold:
@@ -128,15 +140,15 @@ pub struct Hunk {
128140

129141
impl Hunk {
130142
/// Can be used instead of `Option::None` for better performance
131-
/// because lines larger than `i32::MAX` are not supported by imara-diff anways.
143+
/// because lines larger then `i32::MAX` are not supported by imara-diff anways.
132144
/// Has some nice properties where it usually is not necessary to check for `None` seperatly:
133-
/// Empty ranges fail contains checks and also fails smaller than checks.
145+
/// Empty ranges fail contains checks and also faills smaller then checks.
134146
pub const NONE: Hunk = Hunk {
135147
before: u32::MAX..u32::MAX,
136148
after: u32::MAX..u32::MAX,
137149
};
138150

139-
/// Inverts a change so that `before` becomes `after` and `after` becomes `before`
151+
/// Inverts a change so that `before`
140152
pub fn invert(&self) -> Hunk {
141153
Hunk {
142154
before: self.after.clone(),

helix-vcs/src/diff/worker.rs

+52-40
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ use helix_core::{Rope, RopeSlice};
66
use imara_diff::intern::InternedInput;
77
use parking_lot::Mutex;
88
use tokio::sync::mpsc::UnboundedReceiver;
9-
use tokio::sync::{Notify, RwLockReadGuard};
10-
use tokio::time::{timeout, timeout_at, Duration, Instant};
9+
use tokio::sync::Notify;
10+
use tokio::time::{timeout, timeout_at, Duration};
1111

1212
use crate::diff::{
13-
Event, RedrawHandle, RenderStrategy, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC,
14-
DIFF_DEBOUNCE_TIME_SYNC, SYNC_DIFF_TIMEOUT,
13+
Event, RenderLock, ALGORITHM, DIFF_DEBOUNCE_TIME_ASYNC, DIFF_DEBOUNCE_TIME_SYNC,
1514
};
1615

1716
use super::line_cache::InternedRopeLines;
@@ -24,18 +23,18 @@ pub(super) struct DiffWorker {
2423
pub channel: UnboundedReceiver<Event>,
2524
pub hunks: Arc<Mutex<Vec<Hunk>>>,
2625
pub new_hunks: Vec<Hunk>,
27-
pub redraw_handle: RedrawHandle,
26+
pub redraw_notify: Arc<Notify>,
2827
pub difff_finished_notify: Arc<Notify>,
2928
}
3029

3130
impl DiffWorker {
3231
async fn accumulate_events(&mut self, event: Event) -> (Option<Rope>, Option<Rope>) {
33-
let mut accumulator = EventAccumulator::new(&self.redraw_handle);
32+
let mut accumulator = EventAccumulator::new();
3433
accumulator.handle_event(event).await;
3534
accumulator
3635
.accumulate_debounced_events(
3736
&mut self.channel,
38-
self.redraw_handle.clone(),
37+
self.redraw_notify.clone(),
3938
self.difff_finished_notify.clone(),
4039
)
4140
.await;
@@ -91,24 +90,18 @@ impl DiffWorker {
9190
}
9291
}
9392

94-
struct EventAccumulator<'a> {
93+
struct EventAccumulator {
9594
diff_base: Option<Rope>,
9695
doc: Option<Rope>,
97-
render_stratagey: RenderStrategy,
98-
redraw_handle: &'a RedrawHandle,
99-
render_lock: Option<RwLockReadGuard<'a, ()>>,
100-
timeout: Instant,
96+
render_lock: Option<RenderLock>,
10197
}
10298

103-
impl<'a> EventAccumulator<'a> {
104-
fn new(redraw_handle: &'a RedrawHandle) -> EventAccumulator<'a> {
99+
impl<'a> EventAccumulator {
100+
fn new() -> EventAccumulator {
105101
EventAccumulator {
106102
diff_base: None,
107103
doc: None,
108-
render_stratagey: RenderStrategy::Async,
109104
render_lock: None,
110-
redraw_handle,
111-
timeout: Instant::now(),
112105
}
113106
}
114107

@@ -121,26 +114,34 @@ impl<'a> EventAccumulator<'a> {
121114

122115
*dst = Some(event.text);
123116

124-
// always prefer the most synchronous requested render mode
125-
if event.render_strategy > self.render_stratagey {
126-
if self.render_lock.is_none() {
127-
self.timeout = Instant::now() + Duration::from_millis(SYNC_DIFF_TIMEOUT);
128-
self.render_lock = Some(self.redraw_handle.1.read().await);
117+
// always prefer the most synchronus requested render mode
118+
if let Some(render_lock) = event.render_lock {
119+
match &mut self.render_lock {
120+
Some(RenderLock { timeout, .. }) => {
121+
// A timeout of `None` means that the render should
122+
// always wait for the diff to complete (so no timeout)
123+
// remove the existing timeout, otherwise keep the previous timeout
124+
// because it will be shorter then the current timeout
125+
if render_lock.timeout.is_none() {
126+
timeout.take();
127+
}
128+
}
129+
None => self.render_lock = Some(render_lock),
129130
}
130-
self.render_stratagey = event.render_strategy
131131
}
132132
}
133133

134134
async fn accumulate_debounced_events(
135135
&mut self,
136136
channel: &mut UnboundedReceiver<Event>,
137-
redraw_handle: RedrawHandle,
137+
redraw_notify: Arc<Notify>,
138138
diff_finished_notify: Arc<Notify>,
139139
) {
140140
let async_debounce = Duration::from_millis(DIFF_DEBOUNCE_TIME_ASYNC);
141141
let sync_debounce = Duration::from_millis(DIFF_DEBOUNCE_TIME_SYNC);
142142
loop {
143-
let debounce = if self.render_stratagey == RenderStrategy::Async {
143+
// if we are not blocking rendering use a much longer timeout
144+
let debounce = if self.render_lock.is_none() {
144145
async_debounce
145146
} else {
146147
sync_debounce
@@ -154,24 +155,31 @@ impl<'a> EventAccumulator<'a> {
154155
}
155156

156157
// setup task to trigger the rendering
157-
// with the chosen render stragey
158-
match self.render_stratagey {
159-
RenderStrategy::Async => {
158+
// with the choosen render stragey
159+
match self.render_lock.take() {
160+
// diff is performed outside of the rendering loop
161+
// request a redraw after the diff is done
162+
None => {
160163
tokio::spawn(async move {
161164
diff_finished_notify.notified().await;
162-
redraw_handle.0.notify_one();
165+
redraw_notify.notify_one();
163166
});
164167
}
165-
RenderStrategy::SyncWithTimeout => {
166-
let timeout = self.timeout;
168+
// diff is performed inside the rendering loop
169+
// block redraw until the diff is done or the timeout is expired
170+
Some(RenderLock {
171+
lock,
172+
timeout: Some(timeout),
173+
}) => {
167174
tokio::spawn(async move {
168175
let res = {
169-
// Acquire a lock on the redraw handle.
170-
// The lock will block the rendering from occurring while held.
176+
// Aquire a lock on the redraw handle.
177+
// The lock will block the rendering from occuring while held.
171178
// The rendering waits for the diff if it doesn't time out
172-
let _render_guard = redraw_handle.1.read();
173179
timeout_at(timeout, diff_finished_notify.notified()).await
174180
};
181+
// we either reached the timeout or the diff is finished, release the render lock
182+
drop(lock);
175183
if res.is_ok() {
176184
// Diff finished in time we are done.
177185
return;
@@ -180,15 +188,19 @@ impl<'a> EventAccumulator<'a> {
180188
// and wait until the diff occurs to trigger an async redraw
181189
log::warn!("Diff computation timed out, update of diffs might appear delayed");
182190
diff_finished_notify.notified().await;
183-
redraw_handle.0.notify_one();
191+
redraw_notify.notify_one();
184192
});
185193
}
186-
RenderStrategy::Sync => {
194+
// a blocking diff is performed inside the rendering loop
195+
// block redraw until the diff is done
196+
Some(RenderLock {
197+
lock,
198+
timeout: None,
199+
}) => {
187200
tokio::spawn(async move {
188-
// Aquire a lock on the redraw handle.
189-
// The lock will block the rendering from occuring while held.
190-
let _render_guard = redraw_handle.1.read();
191-
diff_finished_notify.notified().await
201+
diff_finished_notify.notified().await;
202+
// diff is done release the lock
203+
drop(lock)
192204
});
193205
}
194206
};

helix-vcs/src/diff/worker/test.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::sync::Arc;
2-
31
use helix_core::Rope;
42
use tokio::task::JoinHandle;
53

@@ -10,7 +8,7 @@ impl DiffHandle {
108
DiffHandle::new_with_handle(
119
Rope::from_str(diff_base),
1210
Rope::from_str(doc),
13-
Arc::default(),
11+
Default::default(),
1412
)
1513
}
1614
async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {

helix-view/src/editor.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ pub struct Config {
154154
)]
155155
pub idle_timeout: Duration,
156156
/// Time in milliseconds since last keypress before a redraws trigger.
157-
/// Used for redrawing asynchronsouly computed UI components, set to 0 for instant.
157+
/// Used for redrawing asynchronsouly computed UI compoenents, set to 0 for instant.
158158
/// Defaults to 100ms.
159159
#[serde(
160160
serialize_with = "serialize_duration_millis",
@@ -733,11 +733,11 @@ pub struct Editor {
733733
/// Allows asynchronous tasks to control the rendering
734734
/// The `Notify` allows asynchronous tasks to request the editor to perform a redraw
735735
/// The `RwLock` blocks the editor from performing the render until an exclusive lock can be aquired
736-
pub redraw_handle: Arc<(Notify, RwLock<()>)>,
736+
pub redraw_handle: RedrawHandle,
737737
pub needs_redraw: bool,
738738
}
739739

740-
pub type RedrawHandle = Arc<(Notify, RwLock<()>)>;
740+
pub type RedrawHandle = (Arc<Notify>, Arc<RwLock<()>>);
741741

742742
#[derive(Debug)]
743743
pub enum EditorEvent {
@@ -830,7 +830,7 @@ impl Editor {
830830
auto_pairs,
831831
exit_code: 0,
832832
config_events: unbounded_channel(),
833-
redraw_handle: Arc::default(),
833+
redraw_handle: Default::default(),
834834
needs_redraw: false,
835835
}
836836
}

0 commit comments

Comments
 (0)