Skip to content

Commit 68a6f15

Browse files
committed
Merge branch 'master' of https://github.com/apache/iotdb into improve-pipe-receiver-session-handling
2 parents 6d58793 + 1c20df4 commit 68a6f15

82 files changed

Lines changed: 3215 additions & 923 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/basic/IoTDBPipeReceiverAutoCreateDisabledIT.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,43 @@ public void testAutoSplitHistoryTsFileWithDeletionWhenReceiverAutoCreateSchemaDi
182182
new HashSet<>(Arrays.asList("root.sg.aligned,true,null,INF,")));
183183
}
184184

185+
@Test
186+
public void
187+
testAutoSplitHistoryTsFileWithUnflushedAlignedDeletionWhenReceiverAutoCreateSchemaDisabled()
188+
throws Exception {
189+
TestUtils.executeNonQueries(
190+
senderEnv,
191+
Arrays.asList(
192+
"create database root.sg",
193+
"create aligned timeseries root.sg.aligned(s0 INT32, s1 INT64, s2 DOUBLE)",
194+
"insert into root.sg.aligned(time, s0, s1, s2) aligned "
195+
+ "values(1, 1, 10, 1.0), (2, 2, 20, 2.0), (3, 3, 30, 3.0)",
196+
"delete timeseries root.sg.aligned.s1"));
197+
198+
TestUtils.executeNonQuery(
199+
senderEnv,
200+
String.format(
201+
"create pipe test with source ('inclusion'='all', 'source.history.enable'='true', 'source.realtime.mode'='batch') "
202+
+ "with sink ('sink'='iotdb-thrift-sink', 'sink.node-urls'='%s')",
203+
receiverEnv.getDataNodeWrapper(0).getIpAndPortString()));
204+
205+
TestUtils.assertDataEventuallyOnEnv(
206+
receiverEnv,
207+
"select s0,s2 from root.sg.aligned",
208+
"Time,root.sg.aligned.s0,root.sg.aligned.s2,",
209+
new HashSet<>(Arrays.asList("1,1,1.0,", "2,2,2.0,", "3,3,3.0,")));
210+
TestUtils.assertDataEventuallyOnEnv(
211+
receiverEnv,
212+
"count timeseries root.sg.aligned.*",
213+
"count(timeseries),",
214+
new HashSet<>(Arrays.asList("2,")));
215+
TestUtils.assertDataEventuallyOnEnv(
216+
receiverEnv,
217+
"show devices root.sg.aligned",
218+
"Device,IsAligned,Template,TTL(ms),",
219+
new HashSet<>(Arrays.asList("root.sg.aligned,true,null,INF,")));
220+
}
221+
185222
private QueryResult queryForResult(final Statement statement, final String sql)
186223
throws SQLException {
187224
try (final ResultSet resultSet = statement.executeQuery(sql)) {

integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBDeletionTableIT.java

Lines changed: 549 additions & 16 deletions
Large diffs are not rendered by default.

integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBLoadConfigurationTableIT.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
package org.apache.iotdb.relational.it.db.it;
2121

22+
import org.apache.iotdb.isession.ITableSession;
23+
import org.apache.iotdb.isession.SessionDataSet;
2224
import org.apache.iotdb.it.env.EnvFactory;
2325
import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
2426
import org.apache.iotdb.it.framework.IoTDBTestRunner;
@@ -40,6 +42,8 @@
4042
import java.nio.file.StandardOpenOption;
4143
import java.sql.Connection;
4244
import java.sql.Statement;
45+
import java.util.HashMap;
46+
import java.util.Map;
4347

4448
@RunWith(IoTDBTestRunner.class)
4549
@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
@@ -82,4 +86,94 @@ public void loadConfiguration() throws IOException {
8286
}
8387
}
8488
}
89+
90+
// `show configuration` must display the in-memory effective value, not the raw config-file
91+
// value. Several hot-reloaded parameters have setters that rewrite the loaded value (e.g.
92+
// `if (x > 0) setX(x)` skips non-positive values; loadFixedSizeLimitForQuery rewrites `<=0`
93+
// to a computed default). This test verifies those keys show the effective value after
94+
// `load configuration`.
95+
@Test
96+
public void showConfigurationDisplaysEffectiveValue() throws Exception {
97+
DataNodeWrapper dataNodeWrapper = EnvFactory.getEnv().getDataNodeWrapper(0);
98+
String confPath =
99+
dataNodeWrapper.getNodePath()
100+
+ File.separator
101+
+ "conf"
102+
+ File.separator
103+
+ "iotdb-system.properties";
104+
105+
// Case 1: non-positive guard params (`if (x > 0) setX(x)`) must display the effective
106+
// default value, not the raw file value (0 / -1).
107+
Map<String, String> afterGuardReload =
108+
appendLinesLoadAndShow(
109+
confPath,
110+
"cte_buffer_size_in_bytes=0",
111+
"max_rows_in_cte_buffer=-1",
112+
"max_sub_task_num_for_information_table_scan=0");
113+
Assert.assertEquals("131072", afterGuardReload.get("cte_buffer_size_in_bytes"));
114+
Assert.assertEquals("1000", afterGuardReload.get("max_rows_in_cte_buffer"));
115+
Assert.assertEquals("4", afterGuardReload.get("max_sub_task_num_for_information_table_scan"));
116+
117+
// Case 2: a valid value is not rewritten, so display must equal the file value (regression).
118+
Map<String, String> afterValidReload =
119+
appendLinesLoadAndShow(confPath, "cte_buffer_size_in_bytes=262144");
120+
Assert.assertEquals("262144", afterValidReload.get("cte_buffer_size_in_bytes"));
121+
122+
// Case 3: params whose template default is 0 are always rewritten to a computed default,
123+
// so they must never display 0 (this was always wrong, even without a bad file value).
124+
Map<String, String> defaultsReload = appendLinesLoadAndShow(confPath);
125+
assertPositiveNonZero(defaultsReload, "sort_buffer_size_in_bytes");
126+
assertPositiveNonZero(defaultsReload, "mods_cache_size_limit_per_fi_in_bytes");
127+
}
128+
129+
// Appends the given lines to the config file, runs `load configuration`, fetches the
130+
// `show configuration` result, and restores the file to its original length.
131+
private Map<String, String> appendLinesLoadAndShow(String confPath, String... lines)
132+
throws Exception {
133+
long length = new File(confPath).length();
134+
try {
135+
if (lines.length > 0) {
136+
try (FileWriter fileWriter = new FileWriter(confPath, true)) {
137+
fileWriter.write(System.lineSeparator());
138+
for (String line : lines) {
139+
fileWriter.write(line);
140+
fileWriter.write(System.lineSeparator());
141+
}
142+
}
143+
}
144+
try (Connection connection = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
145+
Statement statement = connection.createStatement()) {
146+
statement.execute("LOAD CONFIGURATION");
147+
}
148+
return fetchShowConfiguration();
149+
} finally {
150+
try (FileChannel fileChannel =
151+
FileChannel.open(new File(confPath).toPath(), StandardOpenOption.WRITE)) {
152+
fileChannel.truncate(length);
153+
}
154+
}
155+
}
156+
157+
private Map<String, String> fetchShowConfiguration() throws Exception {
158+
Map<String, String> result = new HashMap<>();
159+
try (ITableSession tableSessionConnection = EnvFactory.getEnv().getTableSessionConnection()) {
160+
SessionDataSet sessionDataSet =
161+
tableSessionConnection.executeQueryStatement("show configuration");
162+
SessionDataSet.DataIterator iterator = sessionDataSet.iterator();
163+
while (iterator.next()) {
164+
String name = iterator.getString(1);
165+
String value = iterator.isNull(2) ? null : iterator.getString(2);
166+
result.put(name, value);
167+
}
168+
}
169+
return result;
170+
}
171+
172+
private static void assertPositiveNonZero(Map<String, String> configMap, String key) {
173+
String value = configMap.get(key);
174+
Assert.assertNotNull("show configuration is missing key: " + key, value);
175+
long parsed = Long.parseLong(value);
176+
Assert.assertTrue(
177+
key + " should display a positive effective value, but was " + value, parsed > 0);
178+
}
85179
}

iotdb-client/jdbc/src/main/java/org/apache/iotdb/jdbc/StringUtils.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ public class StringUtils {
4444

4545
static final int WILD_COMPARE_NO_MATCH = -1;
4646

47+
/**
48+
* Maximum absolute {@link BigDecimal#scale()} accepted by {@link #consistentToString(BigDecimal)}
49+
* before it falls back to scientific notation.
50+
*
51+
* <p>{@link BigDecimal#toPlainString()} expands the value to a non-scientific decimal string
52+
* whose length grows with {@code |scale|}. A value such as {@code 1e1000000000} would therefore
53+
* materialize a ~1GB {@link String}, causing {@link OutOfMemoryError} / denial of service on the
54+
* client. Any scale beyond this bound is rejected for expansion and the caller gets the
55+
* scientific form instead, which is a few dozen characters at most.
56+
*/
57+
static final int MAX_PLAIN_STRING_SCALE = 10_000;
58+
4759
static {
4860
for (int i = -128; i <= 127; i++) {
4961
allBytes[i - -128] = (byte) i;
@@ -65,9 +77,16 @@ public static String consistentToString(BigDecimal decimal) {
6577
if (decimal == null) {
6678
return null;
6779
}
80+
// Guard against CVE-style DoS: BigDecimal values constructed from extreme scientific
81+
// notation (e.g. "1e1000000000") have a huge |scale| and would otherwise cause
82+
// toPlainString() to allocate a multi-GB String and OOM the JVM. Fall back to the
83+
// scientific form, which is always short.
84+
if (decimal.scale() > MAX_PLAIN_STRING_SCALE || decimal.scale() < -MAX_PLAIN_STRING_SCALE) {
85+
return decimal.toString();
86+
}
6887
if (toPlainStringMethod != null) {
6988
try {
70-
return (String) toPlainStringMethod.invoke(decimal, null);
89+
return (String) toPlainStringMethod.invoke(decimal, (Object[]) null);
7190
} catch (InvocationTargetException | IllegalAccessException e) {
7291
LOGGER.warn(JdbcMessages.CONSISTENT_TO_STRING_ERROR, e);
7392
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iotdb.jdbc;
21+
22+
import org.junit.Test;
23+
24+
import java.math.BigDecimal;
25+
26+
import static org.junit.Assert.assertEquals;
27+
import static org.junit.Assert.assertNull;
28+
import static org.junit.Assert.assertTrue;
29+
30+
public class StringUtilsTest {
31+
32+
@Test
33+
public void consistentToStringHandlesNull() {
34+
assertNull(StringUtils.consistentToString(null));
35+
}
36+
37+
@Test
38+
public void consistentToStringExpandsNormalDecimal() {
39+
assertEquals("123.45", StringUtils.consistentToString(new BigDecimal("123.45")));
40+
// Plain-string form, not scientific notation.
41+
assertEquals("100", StringUtils.consistentToString(new BigDecimal("1E+2")));
42+
}
43+
44+
@Test
45+
public void consistentToStringExpandsUpToLimit() {
46+
// scale right at the boundary must still be expanded to plain form.
47+
BigDecimal atLimit = new BigDecimal("1E+" + StringUtils.MAX_PLAIN_STRING_SCALE);
48+
String out = StringUtils.consistentToString(atLimit);
49+
assertEquals(StringUtils.MAX_PLAIN_STRING_SCALE + 1, out.length());
50+
assertTrue(out.startsWith("1"));
51+
}
52+
53+
/**
54+
* Regression test for the BigDecimal DoS: {@code new BigDecimal("1e1000000000")} used to cause
55+
* {@link BigDecimal#toPlainString()} to allocate a ~1GB string and OOM the JVM. The guard must
56+
* refuse to expand such values and return the compact scientific form instead.
57+
*/
58+
@Test
59+
public void consistentToStringRefusesToExpandHugeScale() {
60+
BigDecimal malicious = new BigDecimal("1e1000000000");
61+
String out = StringUtils.consistentToString(malicious);
62+
// Must be short (scientific notation), never the ~1e9-char plain form.
63+
assertTrue("expected scientific form, got length " + out.length(), out.length() < 64);
64+
assertTrue(out.contains("E") || out.contains("e"));
65+
}
66+
67+
@Test
68+
public void consistentToStringRefusesToExpandHugePositiveScale() {
69+
// Very large positive scale -> also produces a long plain string; must be refused.
70+
BigDecimal malicious = new BigDecimal("1e-1000000000");
71+
String out = StringUtils.consistentToString(malicious);
72+
assertTrue("expected scientific form, got length " + out.length(), out.length() < 64);
73+
}
74+
}

iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,8 @@ public final class ProcedureMessages {
915915
"Start rollback Renaming table {}.{} on configNode";
916916
public static final String START_ROLLBACK_SET_PROPERTIES_TO_TABLE =
917917
"Start rollback set properties to table {}.{}";
918+
public static final String STATE_MACHINE_PROCEDURE_EOF_STATE_SKIP_EXECUTION =
919+
"StateMachineProcedure pid={} is scheduled with EOF state, skip execution: {}";
918920
public static final String STATE_STUCK_AT = "State stuck at ";
919921
public static final String STOPPIPEPROCEDUREV2_EXECUTEFROMCALCULATEINFOFORTASK =
920922
"StopPipeProcedureV2: executeFromCalculateInfoForTask({})";

iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,8 @@ public final class ProcedureMessages {
913913
"在 ConfigNode 上开始回滚重命名表 {}.{}";
914914
public static final String START_ROLLBACK_SET_PROPERTIES_TO_TABLE =
915915
"开始回滚设置表 {}.{} 的属性";
916+
public static final String STATE_MACHINE_PROCEDURE_EOF_STATE_SKIP_EXECUTION =
917+
"StateMachineProcedure pid={} 被 EOF 状态调度,跳过执行:{}";
916918
public static final String STATE_STUCK_AT = "状态卡在 ";
917919
public static final String STOPPIPEPROCEDUREV2_EXECUTEFROMCALCULATEINFOFORTASK =
918920
"StopPipeProcedureV2: executeFromCalculateInfoForTask({})";

iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/StateMachineProcedure.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,17 @@ protected Procedure<Env>[] execute(final Env env) throws InterruptedException {
146146
updateTimestamp();
147147
try {
148148
if (noMoreState() || isFailed()) {
149-
return null;
149+
return new Procedure[0];
150150
}
151151

152152
TState state = getCurrentState();
153+
if (state == null) {
154+
LOG.warn(
155+
ProcedureMessages.STATE_MACHINE_PROCEDURE_EOF_STATE_SKIP_EXECUTION, getProcId(), this);
156+
stateFlow = Flow.NO_MORE_STATE;
157+
setStateDeserialized(false);
158+
return new Procedure[0];
159+
}
153160

154161
// init for the first execution
155162
if (states.isEmpty()) {
@@ -169,7 +176,9 @@ protected Procedure<Env>[] execute(final Env env) throws InterruptedException {
169176
subProcList.clear();
170177
return subProcedures;
171178
}
172-
return (isWaiting() || isFailed() || noMoreState()) ? null : new Procedure[] {this};
179+
return (isWaiting() || isFailed() || noMoreState())
180+
? new Procedure[0]
181+
: new Procedure[] {this};
173182
} finally {
174183
updateTimestamp();
175184
}

0 commit comments

Comments
 (0)