We had a frequent panic when the agent was using our edit file tool. The root cause was that we were constructing a `BufferDiff` with `BufferDiff::new`, then calling `set_base_text`, but not waiting for that asynchronous operation to finish. This means there was a window of time where the diff's base text was set to the initial value of `""`--that's not a problem in itself, but it was possible for us to call `PendingDiff::update` during that window, which calls `BufferDiff::update_diff`, which calls `BufferDiffSnapshot::new_with_base_buffer`, which takes two arguments `base_text` and `base_text_snapshot` that are supposed to represent the same text. We were getting the first of those arguments from the `base_text` field of `PendingDiff`, which is set immediately to the target base text without waiting for `BufferDiff::set_base_text` to run to completion; and the second from the `BufferDiff` itself, which still has the empty base text during that window. As a result of that mismatch, we could end up adding `DeletedHunk` diff transforms to the multibuffer for the diff card even though the multibuffer's base text was empty, ultimately leading to a panic very far away in rendering code. I've fixed this by adding a new `BufferDiff` constructor for the case where the buffer contents and the base text are (initially) the same, like for the diff cards, and so we don't need an async diff calculation. I also added a debug assertion to catch the basic issue here earlier, when `BufferDiffSnapshot::new_with_base_buffer` is called with two base texts that don't match. Release Notes: - N/A ---------
Showing the changes to crates/buffer_diff/src/buffer_diff.rs (1 of 2 files). Show all
Unified · Split
| @@ -162,6 +162,22 @@ impl BufferDiffSnapshot { · 161 unmodified lines | |||
| 162 | } | 162 | } |
| 163 | } | 163 | } |
| 164 | 164 | ||
| 165 | fn unchanged( | ||
| 166 | buffer: &text::BufferSnapshot, | ||
| 167 | base_text: language::BufferSnapshot, | ||
| 168 | ) -> BufferDiffSnapshot { | ||
| 169 | debug_assert_eq!(buffer.text(), base_text.text()); | ||
| 170 | BufferDiffSnapshot { | ||
| 171 | inner: BufferDiffInner { | ||
| 172 | base_text, | ||
| 173 | hunks: SumTree::new(buffer), | ||
| 174 | pending_hunks: SumTree::new(buffer), | ||
| 175 | base_text_exists: false, | ||
| 176 | }, | ||
| 177 | secondary_diff: None, | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 165 | fn new_with_base_text( | 181 | fn new_with_base_text( |
| 166 | buffer: text::BufferSnapshot, | 182 | buffer: text::BufferSnapshot, |
| 167 | base_text: Option<Arc<String>>, | 183 | base_text: Option<Arc<String>>, |
| @@ -213,7 +229,10 @@ impl BufferDiffSnapshot { · 45 unmodified lines | |||
| 213 | cx: &App, | 229 | cx: &App, |
| 214 | ) -> impl Future<Output = Self> + use<> { | 230 | ) -> impl Future<Output = Self> + use<> { |
| 215 | let base_text_exists = base_text.is_some(); | 231 | let base_text_exists = base_text.is_some(); |
| 216 | let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone())); | 232 | let base_text_pair = base_text.map(|text| { |
| 233 | debug_assert_eq!(&*text, &base_text_snapshot.text()); | ||
| 234 | (text, base_text_snapshot.as_rope().clone()) | ||
| 235 | }); | ||
| 217 | cx.background_executor() | 236 | cx.background_executor() |
| 218 | .spawn_labeled(*CALCULATE_DIFF_TASK, async move { | 237 | .spawn_labeled(*CALCULATE_DIFF_TASK, async move { |
| 219 | Self { | 238 | Self { |
| @@ -873,6 +892,18 @@ impl BufferDiff { · 653 unmodified lines | |||
| 873 | } | 892 | } |
| 874 | } | 893 | } |
| 875 | 894 | ||
| 895 | pub fn new_unchanged( | ||
| 896 | buffer: &text::BufferSnapshot, | ||
| 897 | base_text: language::BufferSnapshot, | ||
| 898 | ) -> Self { | ||
| 899 | debug_assert_eq!(buffer.text(), base_text.text()); | ||
| 900 | BufferDiff { | ||
| 901 | buffer_id: buffer.remote_id(), | ||
| 902 | inner: BufferDiffSnapshot::unchanged(buffer, base_text).inner, | ||
| 903 | secondary_diff: None, | ||
| 904 | } | ||
| 905 | } | ||
| 906 | |||
| 876 | #[cfg(any(test, feature = "test-support"))] | 907 | #[cfg(any(test, feature = "test-support"))] |
| 877 | pub fn new_with_base_text( | 908 | pub fn new_with_base_text( |
| 878 | base_text: &str, | 909 | base_text: &str, |