diff --git a/src/bindings/text-buffer-wrapper.cc b/src/bindings/text-buffer-wrapper.cc index 820cb991..5e7719e2 100644 --- a/src/bindings/text-buffer-wrapper.cc +++ b/src/bindings/text-buffer-wrapper.cc @@ -623,6 +623,11 @@ void TextBufferWrapper::find_words_with_subsequence_in_range(const CallbackInfo } void OnWorkComplete(Napi::Env env, napi_status status) override { + { + std::lock_guard guard(text_buffer_wrapper->outstanding_workers_mutex); + text_buffer_wrapper->outstanding_workers.erase(this); + } + if (status == napi_cancelled) { Callback().Call({env.Null()}); } @@ -631,11 +636,6 @@ void TextBufferWrapper::find_words_with_subsequence_in_range(const CallbackInfo } void Execute() override { - { - std::lock_guard guard(text_buffer_wrapper->outstanding_workers_mutex); - text_buffer_wrapper->outstanding_workers.erase(this); - } - if (!snapshot) { return; } diff --git a/test/js/text-buffer.test.js b/test/js/text-buffer.test.js index c0f8ed8c..e49db86f 100644 --- a/test/js/text-buffer.test.js +++ b/test/js/text-buffer.test.js @@ -13,6 +13,10 @@ const MAX_INT32 = 4294967296 const isWindows = process.platform === 'win32' +async function wait (ms) { + return new Promise(r => setTimeout(r, ms)); +} + const encodings = [ 'big5hkscs', 'cp850', @@ -1261,6 +1265,25 @@ describe('TextBuffer', () => { return Promise.all(promises) }) + it('doesn\'t crash when a job is cancelled', async () => { + function randomChar () { + const chars = "abcdefghijklmnopqrstuvwxyz "; + return chars[Math.floor(Math.random() * chars.length)]; + } + let buffer = new TextBuffer(`lorem ipsum dolor sit amet, consecuetur adipiscing elit`) + // This test triggers a known segfault scenario in which a + // `FindWordsWithSubsequenceInRangeWorker` is created and then needs to + // be cancelled because of a subsequent call to `set_text_in_range`. Just + // like the test above, this one will either pass without making any + // assertions… or crash. + for (let k = 0; k < 100; k++) { + let ch = randomChar() + buffer.findWordsWithSubsequence('lor', '(){} :;,$@%', 20) + buffer.setTextInRange({ start: { row: 0, column: 8 }, end: { row: 0, column: 9 } }, ch) + await wait(Math.round(Math.random() * 20)) + } + }) + it('resolves with all words matching the given query', () => { const buffer = new TextBuffer('banana bandana ban_ana bandaid band bNa\nbanana') return buffer.findWordsWithSubsequence('bna', '_', 4).then((result) => {