Skip to content

Commit ddc637b

Browse files
committed
Merge remote-tracking branch 'origin/main' into vi_egress
# Conflicts: # core/src/main/java/io/questdb/client/cutlass/http/client/WebSocketClient.java
2 parents 941b447 + d54f218 commit ddc637b

16 files changed

Lines changed: 2454 additions & 163 deletions

.github/workflows/rebuild_native_libs.yml

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,12 @@ jobs:
109109
wget -nv https://github.com/Kitware/CMake/releases/download/v3.29.2/cmake-3.29.2-linux-x86_64.tar.gz
110110
tar -zxf cmake-3.29.2-linux-x86_64.tar.gz
111111
echo "PATH=`pwd`/cmake-3.29.2-linux-x86_64/bin/:$PATH" >> "$GITHUB_ENV"
112-
- name: Install JDK17 (for jni.h)
112+
- name: Install GraalVM JDK 25 (for jni.h)
113113
run: |
114-
wget -nv https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.11%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.11_9.tar.gz
115-
tar xfz OpenJDK17U-jdk_x64_linux_hotspot_17.0.11_9.tar.gz
116-
echo "JAVA_HOME=`pwd`/jdk-17.0.11+9/" >> "$GITHUB_ENV"
114+
wget -nv -O graalvm.tar.gz https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz
115+
mkdir graalvm
116+
tar xfz graalvm.tar.gz -C graalvm --strip-components=1
117+
echo "JAVA_HOME=`pwd`/graalvm" >> "$GITHUB_ENV"
117118
- name: Generate Makefiles
118119
run: |
119120
cd ./core
@@ -144,11 +145,12 @@ jobs:
144145
run: |
145146
yum update -y
146147
yum install wget nasm zstd -y
147-
- name: Install JDK17 (for jni.h)
148+
- name: Install GraalVM JDK 25 (for jni.h)
148149
run: |
149-
wget -v --timeout=180 https://api.adoptium.net/v3/binary/version/jdk-17.0.11+9/linux/aarch64/jdk/hotspot/normal/eclipse
150-
tar xfvz eclipse
151-
echo "JAVA_HOME=`pwd`/jdk-17.0.11+9/" >> "$GITHUB_ENV"
150+
wget -v --timeout=180 -O graalvm.tar.gz https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-aarch64_bin.tar.gz
151+
mkdir graalvm
152+
tar xfz graalvm.tar.gz -C graalvm --strip-components=1
153+
echo "JAVA_HOME=`pwd`/graalvm" >> "$GITHUB_ENV"
152154
- name: CMAKE linux-aarch64
153155
run: |
154156
cd ./core
@@ -177,10 +179,16 @@ jobs:
177179
sudo sysctl -w fs.file-max=500000
178180
sudo apt-get update -y
179181
sudo apt-get install -y nasm gcc-mingw-w64 g++-mingw-w64
180-
- name: Download windows jni_md.h from JDK 11
182+
- name: Install GraalVM JDK 25 (for jni.h)
183+
run: |
184+
wget -nv -O graalvm.tar.gz https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz
185+
mkdir graalvm
186+
tar xfz graalvm.tar.gz -C graalvm --strip-components=1
187+
echo "JAVA_HOME=`pwd`/graalvm" >> "$GITHUB_ENV"
188+
- name: Download windows jni_md.h from JDK 25
181189
run: |
182190
cd core
183-
curl https://raw.githubusercontent.com/AdoptOpenJDK/openjdk-jdk11/master/src/java.base/windows/native/include/jni_md.h > $JAVA_HOME/include/jni_md.h
191+
curl https://raw.githubusercontent.com/openjdk/jdk25u/master/src/java.base/windows/native/include/jni_md.h > $JAVA_HOME/include/jni_md.h
184192
- name: CMake Windows
185193
run: |
186194
cd core

ci/run_tests_pipeline.yaml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ stages:
2323
pool:
2424
name: "Azure Pipelines"
2525
vmImage: "ubuntu-latest"
26+
variables:
27+
jdkArch: "x64"
28+
imageName: "ubuntu-latest"
2629
steps:
2730
- checkout: self
2831
fetchDepth: 0
@@ -33,12 +36,7 @@ stages:
3336
git checkout FETCH_HEAD
3437
displayName: "Checkout PR source branch"
3538
condition: eq(variables['Build.Reason'], 'PullRequest')
36-
- task: JavaToolInstaller@0
37-
displayName: "Install Java 17"
38-
inputs:
39-
versionSpec: "17"
40-
jdkArchitectureOption: "x64"
41-
jdkSourceOption: "PreInstalled"
39+
- template: setup.yaml
4240
- bash: mvn -f core/pom.xml javadoc:javadoc -Pjavadoc --batch-mode
4341
displayName: "Verify Javadoc"
4442

ci/setup.yaml

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,48 @@
11
steps:
2-
- task: JavaToolInstaller@0
3-
displayName: "Install Java 17"
4-
inputs:
5-
versionSpec: "17"
6-
jdkArchitectureOption: "$(jdkArch)"
7-
jdkSourceOption: "PreInstalled"
2+
- bash: |
3+
set -eux
4+
case "$(jdkArch)" in
5+
arm64) ARCH="aarch64" ;;
6+
*) ARCH="x64" ;;
7+
esac
8+
curl -fsSL -o "$HOME/graalvm.tar.gz" \
9+
"https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-${ARCH}_bin.tar.gz"
10+
mkdir -p "$HOME/graalvm"
11+
tar -xzf "$HOME/graalvm.tar.gz" -C "$HOME/graalvm" --strip-components=1
12+
echo "##vso[task.setvariable variable=JAVA_HOME]$HOME/graalvm"
13+
echo "##vso[task.prependpath]$HOME/graalvm/bin"
14+
displayName: "Install GraalVM JDK 25 (Linux)"
15+
condition: eq(variables['Agent.OS'], 'Linux')
16+
17+
- bash: |
18+
set -eux
19+
case "$(jdkArch)" in
20+
arm64) ARCH="aarch64" ;;
21+
*) ARCH="x64" ;;
22+
esac
23+
curl -fsSL -o "$HOME/graalvm.tar.gz" \
24+
"https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_macos-${ARCH}_bin.tar.gz"
25+
mkdir -p "$HOME/graalvm"
26+
tar -xzf "$HOME/graalvm.tar.gz" -C "$HOME/graalvm" --strip-components=1
27+
echo "##vso[task.setvariable variable=JAVA_HOME]$HOME/graalvm/Contents/Home"
28+
echo "##vso[task.prependpath]$HOME/graalvm/Contents/Home/bin"
29+
displayName: "Install GraalVM JDK 25 (macOS)"
30+
condition: eq(variables['Agent.OS'], 'Darwin')
31+
32+
- powershell: |
33+
$ErrorActionPreference = "Stop"
34+
$url = "https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_windows-x64_bin.zip"
35+
$zip = Join-Path $env:USERPROFILE "graalvm.zip"
36+
$dest = Join-Path $env:USERPROFILE "graalvm"
37+
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
38+
if (Test-Path $dest) { Remove-Item -Recurse -Force $dest }
39+
Expand-Archive -Path $zip -DestinationPath $dest -Force
40+
$jdkDir = Get-ChildItem -Path $dest -Directory | Select-Object -First 1
41+
$javaHome = $jdkDir.FullName
42+
Write-Host "##vso[task.setvariable variable=JAVA_HOME]$javaHome"
43+
Write-Host "##vso[task.prependpath]$javaHome\bin"
44+
displayName: "Install GraalVM JDK 25 (Windows)"
45+
condition: eq(variables['Agent.OS'], 'Windows_NT')
846
947
- bash: sudo sysctl -w fs.file-max=500000
1048
displayName: "Increase file count on Linux"

core/src/main/java/io/questdb/client/Sender.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@ public int getTimeout() {
619619
private PrivateKey privateKey;
620620
private int protocol = PARAMETER_NOT_SET_EXPLICITLY;
621621
private int protocolVersion = PARAMETER_NOT_SET_EXPLICITLY;
622+
private boolean requestDurableAck;
622623
private int retryTimeoutMillis = PARAMETER_NOT_SET_EXPLICITLY;
623624
private boolean shouldDestroyPrivKey;
624625
private boolean tlsEnabled;
@@ -932,7 +933,8 @@ public Sender build() {
932933
actualAutoFlushIntervalNanos,
933934
actualInFlightWindowSize,
934935
wsAuthHeader,
935-
actualMaxSchemasPerConnection
936+
actualMaxSchemasPerConnection,
937+
requestDurableAck
936938
);
937939
}
938940

@@ -1483,6 +1485,27 @@ public LineSenderBuilder protocolVersion(int protocolVersion) {
14831485
return this;
14841486
}
14851487

1488+
/**
1489+
* Opts the connection in for STATUS_DURABLE_ACK frames. When enabled,
1490+
* servers with primary replication will emit per-table durable-upload
1491+
* watermarks as WAL data reaches the object store.
1492+
* <p>
1493+
* This setting is only supported for WebSocket transport.
1494+
* <p>
1495+
* Observe durable progress via
1496+
* {@link QwpWebSocketSender#getHighestDurableSeqTxn(CharSequence)}.
1497+
*
1498+
* @param enabled true to request durable ACKs
1499+
* @return this instance for method chaining
1500+
*/
1501+
public LineSenderBuilder requestDurableAck(boolean enabled) {
1502+
if (protocol != PARAMETER_NOT_SET_EXPLICITLY && protocol != PROTOCOL_WEBSOCKET) {
1503+
throw new LineSenderException("request_durable_ack is only supported for WebSocket transport");
1504+
}
1505+
this.requestDurableAck = enabled;
1506+
return this;
1507+
}
1508+
14861509
/**
14871510
* Configures the maximum time the Sender will spend retrying upon receiving a recoverable error from the server.
14881511
* <br>
@@ -1875,6 +1898,18 @@ private LineSenderBuilder fromConfig(CharSequence configurationString) {
18751898
pos = getValue(configurationString, pos, sink, "in_flight_window");
18761899
int windowSize = parseIntValue(sink, "in_flight_window");
18771900
inFlightWindowSize(windowSize);
1901+
} else if (Chars.equals("request_durable_ack", sink)) {
1902+
if (protocol != PROTOCOL_WEBSOCKET) {
1903+
throw new LineSenderException("request_durable_ack is only supported for WebSocket transport");
1904+
}
1905+
pos = getValue(configurationString, pos, sink, "request_durable_ack");
1906+
if (Chars.equalsIgnoreCase("on", sink)) {
1907+
requestDurableAck(true);
1908+
} else if (Chars.equalsIgnoreCase("off", sink)) {
1909+
requestDurableAck(false);
1910+
} else {
1911+
throw new LineSenderException("invalid request_durable_ack [value=").put(sink).put(", allowed-values=[on, off]]");
1912+
}
18781913
} else if (Chars.equals("max_schemas_per_connection", sink)) {
18791914
if (protocol != PROTOCOL_WEBSOCKET) {
18801915
throw new LineSenderException("max_schemas_per_connection is only supported for WebSocket transport");
@@ -1972,6 +2007,9 @@ private void validateParameters() {
19722007
.put(", requestedCapacity=").put(bufferCapacity)
19732008
.put("]");
19742009
}
2010+
if (requestDurableAck && protocol != PROTOCOL_WEBSOCKET) {
2011+
throw new LineSenderException("request_durable_ack is only supported for WebSocket transport");
2012+
}
19752013
if (protocol == PROTOCOL_HTTP) {
19762014
if (httpClientConfiguration.getMaximumRequestBufferSize() < httpClientConfiguration.getInitialRequestBufferSize()) {
19772015
throw new LineSenderException("maximum buffer capacity cannot be less than initial buffer capacity ")

core/src/main/java/io/questdb/client/cutlass/http/client/WebSocketClient.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ public abstract class WebSocketClient implements QuietCloseable {
117117
// down to its own hard limit.
118118
private int qwpMaxBatchRows;
119119
private int qwpMaxVersion = 1;
120+
// Opt-in for STATUS_DURABLE_ACK frames; sent as X-QWP-Request-Durable-Ack: true
121+
private boolean qwpRequestDurableAck;
120122
// Receive buffer (native memory)
121123
private long recvBufPtr;
122124
private int recvBufSize;
@@ -422,6 +424,16 @@ public void setQwpMaxVersion(int maxVersion) {
422424
this.qwpMaxVersion = maxVersion;
423425
}
424426

427+
/**
428+
* Enables the opt-in X-QWP-Request-Durable-Ack upgrade header. When set,
429+
* servers with primary replication configured will additionally emit
430+
* STATUS_DURABLE_ACK frames as the WAL containing committed client
431+
* messages reaches the object store.
432+
*/
433+
public void setQwpRequestDurableAck(boolean enabled) {
434+
this.qwpRequestDurableAck = enabled;
435+
}
436+
425437
/**
426438
* Non-blocking attempt to receive a WebSocket frame.
427439
* Returns immediately if no complete frame is available.
@@ -518,6 +530,9 @@ public void upgrade(CharSequence path, int timeout, CharSequence authorizationHe
518530
sendBuffer.putAscii(Integer.toString(qwpMaxBatchRows));
519531
sendBuffer.putAscii("\r\n");
520532
}
533+
if (qwpRequestDurableAck) {
534+
sendBuffer.putAscii("X-QWP-Request-Durable-Ack: true\r\n");
535+
}
521536
if (authorizationHeader != null) {
522537
sendBuffer.putAscii("Authorization: ");
523538
sendBuffer.putAscii(authorizationHeader);

core/src/main/java/io/questdb/client/cutlass/qwp/client/InFlightWindow.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,13 +377,28 @@ public Throwable getLastError() {
377377
return lastError.get();
378378
}
379379

380+
/**
381+
* Returns the highest batch sequence acknowledged by the server, or -1 if
382+
* no acknowledgment has been received yet.
383+
*/
384+
public long getHighestAckedSequence() {
385+
return highestAcked;
386+
}
387+
380388
/**
381389
* Returns the maximum window size.
382390
*/
383391
public int getMaxWindowSize() {
384392
return maxWindowSize;
385393
}
386394

395+
/**
396+
* Returns the timeout (ms) applied to blocking window operations.
397+
*/
398+
public long getTimeoutMs() {
399+
return timeoutMs;
400+
}
401+
387402
/**
388403
* Returns the total number of batches acknowledged.
389404
*/

0 commit comments

Comments
 (0)