Skip to content

Commit bc119ba

Browse files
committed
address review comments and ensure lock is always aquired
1 parent f38829a commit bc119ba

File tree

5 files changed

+95
-74
lines changed

5 files changed

+95
-74
lines changed

helix-term/src/application.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ impl Application {
284284
scroll: None,
285285
};
286286

287-
// Aquire mutable access to the redraw_handle lock
287+
// Acquire mutable access to the redraw_handle lock
288288
// to ensure that there are no tasks running that want to block rendering
289289
drop(cx.editor.redraw_handle.1.write().await);
290290
cx.editor.needs_redraw = false;

helix-vcs/src/diff.rs

+38-26
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 occurring
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,
57-
difff_finished_notify: Arc::default(),
55+
redraw_notify: redraw_handle.0,
56+
diff_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,36 +76,48 @@ 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 acquire 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
103115
/// synchronous debounce value should be low
104116
/// so we can update synchronously 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
@@ -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.
132-
/// 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.
143+
/// because lines larger then `i32::MAX` are not supported by `imara-diff` anyways.
144+
/// Has some nice properties where it usually is not necessary to check for `None` separately:
145+
/// Empty ranges fail contains checks and also fails 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

+51-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,19 +23,19 @@ 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,
28-
pub difff_finished_notify: Arc<Notify>,
26+
pub redraw_notify: Arc<Notify>,
27+
pub diff_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(),
39-
self.difff_finished_notify.clone(),
37+
self.redraw_notify.clone(),
38+
self.diff_finished_notify.clone(),
4039
)
4140
.await;
4241
(accumulator.doc, accumulator.diff_base)
@@ -80,7 +79,7 @@ impl DiffWorker {
8079
/// To improve performance this function tries to reuse the allocation of the old diff previously stored in `self.line_diffs`
8180
fn apply_hunks(&mut self) {
8281
swap(&mut *self.hunks.lock(), &mut self.new_hunks);
83-
self.difff_finished_notify.notify_waiters();
82+
self.diff_finished_notify.notify_waiters();
8483
self.new_hunks.clear();
8584
}
8685

@@ -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

@@ -122,25 +115,33 @@ impl<'a> EventAccumulator<'a> {
122115
*dst = Some(event.text);
123116

124117
// 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);
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,30 @@ 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+
match self.render_lock.take() {
159+
// diff is performed outside of the rendering loop
160+
// request a redraw after the diff is done
161+
None => {
160162
tokio::spawn(async move {
161163
diff_finished_notify.notified().await;
162-
redraw_handle.0.notify_one();
164+
redraw_notify.notify_one();
163165
});
164166
}
165-
RenderStrategy::SyncWithTimeout => {
166-
let timeout = self.timeout;
167+
// diff is performed inside the rendering loop
168+
// block redraw until the diff is done or the timeout is expired
169+
Some(RenderLock {
170+
lock,
171+
timeout: Some(timeout),
172+
}) => {
167173
tokio::spawn(async move {
168174
let res = {
169175
// Acquire a lock on the redraw handle.
170176
// The lock will block the rendering from occurring while held.
171177
// The rendering waits for the diff if it doesn't time out
172-
let _render_guard = redraw_handle.1.read();
173178
timeout_at(timeout, diff_finished_notify.notified()).await
174179
};
180+
// we either reached the timeout or the diff is finished, release the render lock
181+
drop(lock);
175182
if res.is_ok() {
176183
// Diff finished in time we are done.
177184
return;
@@ -180,15 +187,19 @@ impl<'a> EventAccumulator<'a> {
180187
// and wait until the diff occurs to trigger an async redraw
181188
log::warn!("Diff computation timed out, update of diffs might appear delayed");
182189
diff_finished_notify.notified().await;
183-
redraw_handle.0.notify_one();
190+
redraw_notify.notify_one();
184191
});
185192
}
186-
RenderStrategy::Sync => {
193+
// a blocking diff is performed inside the rendering loop
194+
// block redraw until the diff is done
195+
Some(RenderLock {
196+
lock,
197+
timeout: None,
198+
}) => {
187199
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
200+
diff_finished_notify.notified().await;
201+
// diff is done release the lock
202+
drop(lock)
192203
});
193204
}
194205
};

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 asynchronously computed UI components, 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)