Skip to content
This repository was archived by the owner on Oct 18, 2024. It is now read-only.

Commit 8c39b14

Browse files
committed
fix: cancellation flag is shared by all parsers
1 parent c4630fc commit 8c39b14

File tree

3 files changed

+163
-45
lines changed

3 files changed

+163
-45
lines changed

Diff for: android-tree-sitter/src/main/cpp/ts_parser.cc

+115-43
Original file line numberDiff line numberDiff line change
@@ -25,67 +25,153 @@
2525
#include "utils/ts_preconditions.h"
2626
#include "utils/ts_log.h"
2727

28-
static std::mutex cancellation_flag_mutex;
29-
static std::atomic<size_t *> cancellation_flag(nullptr);
28+
/**
29+
* `TSParserInternal` stores the actual tree sitter parser instance along
30+
* with the cancellation flag and the cancellation flag mutex.
31+
*/
32+
class TSParserInternal {
33+
public:
3034

31-
static size_t *get_cancellation_flag() {
32-
std::lock_guard<std::mutex> get_lock(cancellation_flag_mutex);
33-
return cancellation_flag.load();
34-
}
35+
TSParserInternal() {
36+
cancellation_flag_mutex = new std::mutex();
37+
cancellation_flag = new std::atomic<size_t *>(nullptr);
38+
parser = ts_parser_new();
39+
}
3540

36-
static void set_cancellation_flag(size_t *flag) {
37-
std::lock_guard<std::mutex> set_lock(cancellation_flag_mutex);
38-
cancellation_flag.store(flag);
39-
}
41+
~TSParserInternal() {
42+
delete cancellation_flag_mutex;
43+
delete cancellation_flag;
44+
45+
ts_parser_delete(parser);
46+
47+
cancellation_flag_mutex = nullptr;
48+
cancellation_flag = nullptr;
49+
parser = nullptr;
50+
}
51+
52+
TSParser *getParser(JNIEnv *env) {
53+
if (check_destroyed(env)) {
54+
return nullptr;
55+
}
56+
57+
return this->parser;
58+
}
59+
60+
bool begin_round(JNIEnv *env) {
61+
auto flag = get_cancellation_flag(env);
62+
63+
if (flag) {
64+
throw_illegal_state(env,
65+
"Parser is already parsing another syntax tree! You must cancel the current parse first!");
66+
return false;
67+
}
68+
69+
// allocate a new cancellation flag
70+
flag = (size_t *) malloc(sizeof(int));
71+
set_cancellation_flag(env, flag);
72+
73+
// set the cancellation flag to '0' to indicate that the parser should continue parsing
74+
*flag = 0;
75+
ts_parser_set_cancellation_flag(getParser(env), flag);
76+
77+
return true;
78+
}
79+
80+
void end_round(JNIEnv *env) {
81+
82+
size_t *flag = get_cancellation_flag(env);
83+
84+
// release the cancellation flag
85+
free((size_t *) flag);
86+
set_cancellation_flag(env, nullptr);
87+
ts_parser_set_cancellation_flag(getParser(env), nullptr);
88+
}
89+
90+
size_t *get_cancellation_flag(JNIEnv *env) {
91+
if (check_destroyed(env)) {
92+
return nullptr;
93+
}
94+
95+
std::lock_guard<std::mutex> get_lock(*cancellation_flag_mutex);
96+
return cancellation_flag->load();
97+
}
98+
99+
void set_cancellation_flag(JNIEnv *env, size_t *flag) {
100+
if (check_destroyed(env)) {
101+
return;
102+
}
103+
104+
std::lock_guard<std::mutex> set_lock(*cancellation_flag_mutex);
105+
cancellation_flag->store(flag);
106+
}
107+
108+
private:
109+
std::mutex *cancellation_flag_mutex;
110+
std::atomic<size_t *> *cancellation_flag;
111+
112+
TSParser *parser;
113+
114+
bool check_destroyed(JNIEnv *env) {
115+
if (cancellation_flag_mutex == nullptr || cancellation_flag == nullptr || parser == nullptr) {
116+
throw_illegal_state(env, "TSParserInternal has already been destroyed");
117+
return true;
118+
}
119+
120+
return false;
121+
}
122+
};
40123

41124
extern "C" JNIEXPORT jlong JNICALL
42125
Java_com_itsaky_androidide_treesitter_TSParser_00024Native_newParser(
43126
JNIEnv *env, jclass self) {
44-
return (jlong) ts_parser_new();
127+
auto parser = new TSParserInternal;
128+
return (jlong) parser;
45129
}
46130

47131
extern "C" JNIEXPORT void JNICALL
48132
Java_com_itsaky_androidide_treesitter_TSParser_00024Native_delete(
49-
JNIEnv *env, jclass self, jlong parser) {
50-
req_nnp(env, parser);
51-
ts_parser_delete((TSParser *) parser);
133+
JNIEnv *env, jclass self, jlong parser_ptr) {
134+
req_nnp(env, parser_ptr);
135+
136+
auto parser = (TSParserInternal *) parser_ptr;
137+
delete parser;
52138
}
53139

54140
extern "C" JNIEXPORT void JNICALL
55141
Java_com_itsaky_androidide_treesitter_TSParser_00024Native_setLanguage(
56142
JNIEnv *env, jclass self, jlong parser, jlong language) {
57143
req_nnp(env, parser, "parser");
58144
req_nnp(env, language, "language");
59-
ts_parser_set_language((TSParser *) parser, (TSLanguage *) language);
145+
ts_parser_set_language(((TSParserInternal *) parser)->getParser(env), (TSLanguage *) language);
60146
}
61147

62148
extern "C" JNIEXPORT jlong JNICALL
63149
Java_com_itsaky_androidide_treesitter_TSParser_00024Native_getLanguage(
64150
JNIEnv *env, jclass self, jlong parser) {
65151
req_nnp(env, parser);
66-
return (jlong) ts_parser_language((TSParser *) parser);
152+
return (jlong) ts_parser_language(((TSParserInternal *) parser)->getParser(env));
67153
}
68154

69155
extern "C" JNIEXPORT void JNICALL
70156
Java_com_itsaky_androidide_treesitter_TSParser_00024Native_reset(JNIEnv *env,
71157
jclass self,
72158
jlong parser) {
73159
req_nnp(env, parser);
74-
ts_parser_reset((TSParser *) parser);
160+
ts_parser_reset(((TSParserInternal *) parser)->getParser(env));
75161
}
76162

77163
extern "C" JNIEXPORT void JNICALL
78164
Java_com_itsaky_androidide_treesitter_TSParser_00024Native_setTimeout(
79165
JNIEnv *env, jclass self, jlong parser, jlong macros) {
80166
req_nnp(env, parser);
81-
ts_parser_set_timeout_micros((TSParser *) parser, macros);
167+
ts_parser_set_timeout_micros(((TSParserInternal *) parser)->getParser(env), macros);
82168
}
83169

84170
extern "C" JNIEXPORT jlong JNICALL
85171
Java_com_itsaky_androidide_treesitter_TSParser_00024Native_getTimeout(
86172
JNIEnv *env, jclass self, jlong parser) {
87173
req_nnp(env, parser);
88-
return (jlong) ts_parser_timeout_micros((TSParser *) parser);
174+
return (jlong) ts_parser_timeout_micros(((TSParserInternal *) parser)->getParser(env));
89175
}
90176

91177
extern "C" JNIEXPORT jboolean JNICALL
@@ -102,7 +188,7 @@ Java_com_itsaky_androidide_treesitter_TSParser_00024Native_setIncludedRanges(
102188
}
103189

104190
const TSRange *r = tsRanges;
105-
return (jboolean) ts_parser_set_included_ranges((TSParser *) parser,
191+
return (jboolean) ts_parser_set_included_ranges(((TSParserInternal *) parser)->getParser(env),
106192
r,
107193
count);
108194
}
@@ -112,7 +198,7 @@ Java_com_itsaky_androidide_treesitter_TSParser_00024Native_getIncludedRanges(
112198
JNIEnv *env, jclass self, jlong parser) {
113199
req_nnp(env, parser);
114200
jint count;
115-
const TSRange *ranges = ts_parser_included_ranges((TSParser *) parser,
201+
const TSRange *ranges = ts_parser_included_ranges(((TSParserInternal *) parser)->getParser(env),
116202
reinterpret_cast<uint32_t *>(&count));
117203
jobjectArray result = createRangeArr(env, count);
118204
req_nnp(env, result, "TSRange[] from factory");
@@ -133,25 +219,12 @@ Java_com_itsaky_androidide_treesitter_TSParser_00024Native_parse(JNIEnv *env,
133219
jlong str_pointer) {
134220
req_nnp(env, parser);
135221
req_nnp(env, str_pointer, "string");
136-
auto *ts_parser = (TSParser *) parser;
222+
auto *ts_parser_internal = (TSParserInternal *) parser;
223+
TSParser *ts_parser = ts_parser_internal->getParser(env);
137224
TSTree *old_tree = tree_pointer == 0 ? nullptr : (TSTree *) tree_pointer;
138225
auto *source = as_str(str_pointer);
139226

140-
auto flag = get_cancellation_flag();
141-
142-
if (flag) {
143-
throw_illegal_state(env,
144-
"Parser is already parsing another syntax tree! You must cancel the current parse first!");
145-
return 0;
146-
}
147-
148-
// allocate a new cancellation flag
149-
flag = (size_t *) malloc(sizeof(int));
150-
set_cancellation_flag(flag);
151-
152-
// set the cancellation flag to '0' to indicate that the parser should continue parsing
153-
*flag = 0;
154-
ts_parser_set_cancellation_flag(ts_parser, flag);
227+
ts_parser_internal->begin_round(env);
155228

156229
// start parsing
157230
// if the user cancels the parse while this method is being executed
@@ -163,10 +236,7 @@ Java_com_itsaky_androidide_treesitter_TSParser_00024Native_parse(JNIEnv *env,
163236
source->byte_length(),
164237
TSInputEncodingUTF16);
165238

166-
// release the cancellation flag
167-
free((size_t *) flag);
168-
set_cancellation_flag(nullptr);
169-
ts_parser_set_cancellation_flag(ts_parser, nullptr);
239+
ts_parser_internal->end_round(env);
170240

171241
return (jlong) tree;
172242
}
@@ -175,9 +245,11 @@ extern "C"
175245
JNIEXPORT jboolean JNICALL
176246
Java_com_itsaky_androidide_treesitter_TSParser_00024Native_requestCancellation(
177247
JNIEnv *env,
178-
jclass clazz) {
248+
jclass clazz,
249+
jlong parser) {
179250

180-
auto flag = get_cancellation_flag();
251+
auto *parserInternal = (TSParserInternal *) parser;
252+
auto flag = parserInternal->get_cancellation_flag(env);
181253

182254
// no parse is in progress
183255
if (flag == nullptr) {

Diff for: android-tree-sitter/src/main/java/com/itsaky/androidide/treesitter/TSParser.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ protected boolean unsetParsingFlag() {
255255
* otherwise.
256256
*/
257257
public boolean requestCancellationAsync() {
258-
final var requested = Native.requestCancellation();
258+
final var requested = Native.requestCancellation(getNativeObject());
259259
setCancellationRequested(requested);
260260
return requested;
261261
}
@@ -389,6 +389,6 @@ private static class Native {
389389

390390
public static native long parse(long parser, long treePointer, long strPointer);
391391

392-
public static native boolean requestCancellation();
392+
public static native boolean requestCancellation(long parser);
393393
}
394394
}

Diff for: android-tree-sitter/src/test/java/com/itsaky/androidide/treesitter/ParserTest.java

+46
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,52 @@ public void testParserParseCallShouldSucceedIfAnotherParseIsInProgressAndCancell
446446
}
447447
}
448448

449+
@Test
450+
public void testParserParseCallShouldNotFailIfWhenMultipleParsersAreParsing() {
451+
try (final var parser1 = TSParser.create();
452+
final var parser2 = TSParser.create();
453+
final var mainParseContent = UTF16StringFactory.newString()
454+
) {
455+
parser1.setLanguage(TSLanguageJava.getInstance());
456+
parser2.setLanguage(TSLanguageJava.getInstance());
457+
458+
// Read the content before starting the threads
459+
final var fileContent = readResource("View.java.txt");
460+
mainParseContent.append(fileContent);
461+
mainParseContent.append(fileContent);
462+
mainParseContent.append(fileContent);
463+
464+
final var executor = Executors.newScheduledThreadPool(2);
465+
466+
final var parseFuture1 = executor.schedule(() -> {
467+
assertThat(parser1.isParsing()).isFalse();
468+
try (final var tree = parser1.parseString(mainParseContent)) {
469+
assertThat(tree).isNotNull();
470+
}
471+
}, 0, TimeUnit.MICROSECONDS);
472+
473+
final var secondParseDelayMs = 1;
474+
final var parseFuture2 = executor.schedule(() -> {
475+
assertThat(parser2.isParsing()).isFalse();
476+
try (var tree = parser2.parseString(fileContent)) {
477+
assertThat(tree).isNotNull();
478+
} catch (Throwable e) {
479+
throw new RuntimeException(e);
480+
}
481+
}, secondParseDelayMs, TimeUnit.MILLISECONDS);
482+
483+
try {
484+
// both parser instances are independent and hence, both of them must succeed
485+
parseFuture1.get();
486+
parseFuture2.get();
487+
} catch (Throwable e) {
488+
throw new RuntimeException(e);
489+
} finally {
490+
executor.shutdownNow();
491+
}
492+
}
493+
}
494+
449495
@Test
450496
public void testParserParseCallShouldFailIfAnotherParseIsInProgressAndCancellationWasNotRequested() {
451497
try (final var parser = TSParser.create(); final var mainParseContent = UTF16StringFactory.newString()) {

0 commit comments

Comments
 (0)