Skip to content

Commit 9f12584

Browse files
fix concurrency issues in SharedData caches (fixes #2479) (#2593)
1 parent f31ee47 commit 9f12584

File tree

2 files changed

+67
-11
lines changed

2 files changed

+67
-11
lines changed

basex-core/src/main/java/org/basex/query/util/SharedData.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,12 @@ public QNm qName(final byte[] name) {
6464
* @return QName
6565
*/
6666
public QNm qName(final byte[] name, final byte[] uri) {
67-
return qnames.computeIfAbsent(
68-
uri != null ? Token.concat(name, Token.cpToken(' '), uri) : name,
69-
() -> new QNm(name, uri)
70-
);
67+
synchronized(qnames) {
68+
return qnames.computeIfAbsent(
69+
uri != null ? Token.concat(name, Token.cpToken(' '), uri) : name,
70+
() -> new QNm(name, uri)
71+
);
72+
}
7173
}
7274

7375
/**
@@ -76,7 +78,9 @@ public QNm qName(final byte[] name, final byte[] uri) {
7678
* @return shared token
7779
*/
7880
public byte[] token(final byte[] token) {
79-
return token.length == 0 ? Token.EMPTY : tokens.put(token);
81+
synchronized (tokens) {
82+
return token.length == 0 ? Token.EMPTY : tokens.put(token);
83+
}
8084
}
8185

8286
/**
@@ -85,12 +89,14 @@ public byte[] token(final byte[] token) {
8589
* @return new or already registered record type
8690
*/
8791
public RecordType record(final RecordType rt) {
88-
final ArrayList<RecordType> types = recordTypes.computeIfAbsent(rt.fields().size(),
89-
ArrayList::new);
90-
for(final RecordType type : types) {
91-
if(type.equals(rt)) return type;
92+
synchronized(recordTypes) {
93+
final ArrayList<RecordType> types = recordTypes.computeIfAbsent(rt.fields().size(),
94+
ArrayList::new);
95+
for(final RecordType type : types) {
96+
if(type.equals(rt)) return type;
97+
}
98+
types.add(rt);
99+
return rt;
92100
}
93-
types.add(rt);
94-
return rt;
95101
}
96102
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.basex.query.util;
2+
3+
import org.basex.*;
4+
import org.junit.jupiter.api.*;
5+
6+
/**
7+
* XQuery shared data tests.
8+
*
9+
* @author BaseX Team, BSD License
10+
* @author Gunther Rademacher
11+
*/
12+
public final class SharedDataTest extends SandboxTest {
13+
/** Number of test iterations. */
14+
private static final int N = 100;
15+
16+
/** Concurrent usage of SharedData#qnames. */
17+
@Test public void qname() {
18+
query("xquery:fork-join(\n"
19+
+ " for $i in 1 to " + N + "\n"
20+
+ " return fn() { element { 'x' || $i } {} }\n"
21+
+ ")\n"
22+
+ "=> count()", N);
23+
}
24+
25+
/** Concurrent usage of SharedData#tokens. */
26+
@Test public void token() {
27+
query("xquery:fork-join(\n"
28+
+ " for $i in 1 to " + N + "\n"
29+
+ " return fn() {\n"
30+
+ " element { `e{$i}` } {\n"
31+
+ " (1 to $i) ! attribute { `a{.}` } { `{$i}-{.}` }\n"
32+
+ " }\n"
33+
+ " }\n"
34+
+ ")\n"
35+
+ "=> count()", N);
36+
}
37+
38+
/** Concurrent usage of SharedData#recordTypes. */
39+
@Test public void recordType() {
40+
query("xquery:fork-join(\n"
41+
+ " for $i in 1 to " + N + "\n"
42+
+ " return function() { \n"
43+
+ " let $type := 'record(' || string-join((1 to $i) ! ('a' || .), ',') || ')'\n"
44+
+ " let $rec := `{{ {string-join((1 to $i) ! `'a{.}':{.}`, ',')} }}`\n"
45+
+ " return xquery:eval(`fn() as {$type} {{ {$rec} }} ()`)\n"
46+
+ " }\n"
47+
+ ")\n"
48+
+ "=> count()", N);
49+
}
50+
}

0 commit comments

Comments
 (0)