Skip to content

Commit 154fe28

Browse files
Release/26.7.29: configurable connect/read/write timeouts (#379)
* SK-3023 v3-release/26.7.29: configurable timeouts + internal-release loop fix [skip ci] Squashes the release-only history on top of v3 (ceae600) into one commit: - SK-3023 configurable connect/read/write timeouts for v3 (#374) - SK-2986 pass PAT_ACTIONS into reusable workflow via workflow_call secret (#375) - SK-3023 fix internal-release infinite loop via [skip ci] on the bump commit (#377) - the automated dev version bumps (folded in) Version: 3.0.0-beta.12-dev.6d1831b. Tree is unchanged from f2eb507. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * SK-3023 guard internal-release against the automated bump commit (v3 release) Replaces the skip-ci approach with a scoped job guard so the PAT-authored version bump cannot re-trigger the release, without globally skipping CI (so PR checks run again). - internal-release.yml: skip the job when the head commit is an automated bump - shared-build-and-deploy.yml: drop the skip-ci suffix from the bump commit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * [AUTOMATED] Private Release 3.0.0-beta.12-dev-cad9a33 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Devesh-Skyflow <Devesh-Skyflow@users.noreply.github.com>
1 parent ceae600 commit 154fe28

12 files changed

Lines changed: 261 additions & 18 deletions

File tree

.github/workflows/beta-release.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ jobs:
1818
gpg-passphrase: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
1919
skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} >> .env
2020
test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} >> .env
21-
test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env
21+
test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env
22+
pat-actions: ${{ secrets.PAT_ACTIONS }}

.github/workflows/internal-release.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ on:
77
- "*.md"
88
branches:
99
- v3-release/*
10+
# Note: pushes that change no non-.md files (e.g. empty commits) are skipped by the
11+
# paths-ignore filter above and will not trigger this workflow.
1012

1113
jobs:
1214
build-and-deploy-v3:
15+
# Skip the automated version-bump commit so it can't re-trigger this workflow.
16+
# The bump is pushed with a PAT (which, unlike GITHUB_TOKEN, DOES trigger workflows),
17+
# so without this guard the release loops: bump -> push -> release -> bump -> ...
18+
if: ${{ !contains(github.event.head_commit.message, '[AUTOMATED]') }}
1319
uses: ./.github/workflows/shared-build-and-deploy.yml
1420
with:
1521
ref: ${{ github.ref_name }}
@@ -24,4 +30,5 @@ jobs:
2430
gpg-passphrase: ${{ secrets.JFROG_GPG_PASSPHRASE }}
2531
skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} >> .env
2632
test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} >> .env
27-
test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env
33+
test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env
34+
pat-actions: ${{ secrets.PAT_ACTIONS }}

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ jobs:
1818
skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} >> .env
1919
test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} >> .env
2020
test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env
21+
pat-actions: ${{ secrets.PAT_ACTIONS }}

.github/workflows/shared-build-and-deploy.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ on:
5050
test-reusable-token:
5151
required: true
5252

53+
pat-actions:
54+
required: true
55+
5356
jobs:
5457
publish:
5558
runs-on: ubuntu-latest
@@ -61,7 +64,9 @@ jobs:
6164
# checkout and reused for the automated version-bump push below, so
6265
# that push satisfies the branch-protection ruleset's repo-admin
6366
# bypass (github-actions[bot] is not a bypass actor). See SK-2986.
64-
token: ${{ secrets.PAT_ACTIONS }}
67+
# Passed through workflow_call.secrets since reusable workflows do
68+
# not inherit the caller's secrets automatically.
69+
token: ${{ secrets.pat-actions }}
6570

6671
- name: Set up maven or jfrog repository
6772
uses: actions/setup-java@v1
@@ -113,6 +118,10 @@ jobs:
113118
114119
git add v3/pom.xml
115120
if [[ "${{ inputs.tag }}" == "internal" ]]; then
121+
# The '[AUTOMATED]' marker is what stops the release loop: internal-release.yml
122+
# skips any push whose head commit contains it (the bump is pushed with a PAT,
123+
# which — unlike GITHUB_TOKEN — DOES trigger workflows). Keep the marker in sync
124+
# with that guard. This keeps PR CI intact (no global [skip ci]).
116125
git commit -m "[AUTOMATED] Private Release ${{ steps.previoustag.outputs.tag }}-dev-$(git rev-parse --short $GITHUB_SHA)"
117126
git push origin ${{ github.ref_name }} -f
118127
fi

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,19 @@ You can control how long a request is allowed to run and whether failed requests
180180
| Setting | Unit | Default | Description |
181181
| --- | --- | --- | --- |
182182
| `timeout` | seconds | `60` | Overall time budget for a request, including any retries and backoff. |
183+
| `connectTimeout` | seconds | `10` | Time budget for establishing a connection, per attempt. |
184+
| `readTimeout` | seconds | `10` | Time budget for reading the response, per attempt. |
185+
| `writeTimeout` | seconds | `10` | Time budget for writing the request, per attempt. |
183186
| `maxRetries` | count | `0` | Number of retry attempts. `0` disables retries. |
184187
| `initialRetryDelayMillis` | milliseconds | `500` | Base delay before the first retry. |
185188
| `maxRetryDelayMillis` | milliseconds | `2000` | Upper bound on the delay between retries. |
186189

190+
**How the four timeouts relate:** `timeout` is the *total* ceiling for the whole call — every attempt, every backoff sleep, all counted together. `connectTimeout`, `readTimeout`, and `writeTimeout` each bound a single *phase of one attempt* (connecting, reading, writing). Because the phase timeouts are per attempt, their sum across retries can exceed `timeout` — but `timeout` always wins and cuts the call off. In practice, set `timeout` to cap worst-case blocking, and use the phase timeouts to fail faster on a stalled connection or a slow-but-not-dead server. The three phase timeouts default to the underlying HTTP client's `10` seconds; leaving them unset preserves that default.
191+
187192
Each setting is available at two levels:
188193

189-
- **Client-wide** — on `Skyflow.builder()`: `.timeout(int)`, `.maxRetries(int)`, `.initialRetryDelayMillis(long)`, `.maxRetryDelayMillis(long)`. Applies to every vault.
190-
- **Per vault** — on `VaultConfig`: `.setTimeout(int)`, `.setMaxRetries(int)`, `.setInitialRetryDelayMillis(long)`, `.setMaxRetryDelayMillis(long)`. Applies to that vault only.
194+
- **Client-wide** — on `Skyflow.builder()`: `.timeout(int)`, `.connectTimeout(int)`, `.readTimeout(int)`, `.writeTimeout(int)`, `.maxRetries(int)`, `.initialRetryDelayMillis(long)`, `.maxRetryDelayMillis(long)`. Applies to every vault.
195+
- **Per vault** — on `VaultConfig`: `.setTimeout(int)`, `.setConnectTimeout(int)`, `.setReadTimeout(int)`, `.setWriteTimeout(int)`, `.setMaxRetries(int)`, `.setInitialRetryDelayMillis(long)`, `.setMaxRetryDelayMillis(long)`. Applies to that vault only.
191196

192197
**Precedence:** a value set on `VaultConfig` (per vault) overrides the client-wide value set on `Skyflow.builder()`, which overrides the SDK default. Resolution is per field, so a vault can override just `timeout` and still inherit the client-wide retry settings.
193198

@@ -206,21 +211,28 @@ vaultConfig.setClusterId("<CLUSTER_ID>");
206211
vaultConfig.setEnv(Env.PROD);
207212
vaultConfig.setCredentials(credentials);
208213
vaultConfig.setTimeout(30); // seconds — overall request timeout
214+
vaultConfig.setConnectTimeout(5); // seconds — per-attempt connect timeout
215+
vaultConfig.setReadTimeout(20); // seconds — per-attempt read timeout
216+
vaultConfig.setWriteTimeout(5); // seconds — per-attempt write timeout
209217
vaultConfig.setMaxRetries(3); // retry attempts (0 = retries off)
210218
vaultConfig.setInitialRetryDelayMillis(1000L); // base backoff in milliseconds
211219
vaultConfig.setMaxRetryDelayMillis(4000L); // backoff cap in milliseconds
212220

213221
// Client-wide defaults: apply to every vault unless overridden on the vault (as above).
214222
Skyflow skyflowClient = Skyflow.builder()
215223
.timeout(60) // seconds — overall request timeout
224+
.connectTimeout(10) // seconds — per-attempt connect timeout
225+
.readTimeout(15) // seconds — per-attempt read timeout
226+
.writeTimeout(10) // seconds — per-attempt write timeout
216227
.maxRetries(2) // retry attempts (0 = retries off)
217228
.initialRetryDelayMillis(500L) // base backoff in milliseconds
218229
.maxRetryDelayMillis(2000L) // backoff cap in milliseconds
219230
.addVaultConfig(vaultConfig)
220231
.build();
221232

222-
// Result for this vault: timeout=30, maxRetries=3, initialRetryDelayMillis=1000, maxRetryDelayMillis=4000
223-
// (all overridden per vault). A vault that sets none of these inherits the client-wide values above.
233+
// Result for this vault: timeout=30, connectTimeout=5, readTimeout=20, writeTimeout=5,
234+
// maxRetries=3, initialRetryDelayMillis=1000, maxRetryDelayMillis=4000 (all overridden per vault).
235+
// A vault that sets none of these inherits the client-wide values above.
224236
```
225237

226238
# Vault

samples/src/main/java/com/example/vault/TimeoutAndRetryConfigExample.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
* <ul>
1515
* <li>{@code timeout} – overall call timeout in <b>seconds</b> (bounds the whole
1616
* request including retries and backoff). Default: 60.</li>
17+
* <li>{@code connectTimeout} – per-attempt connection-establishment timeout in
18+
* <b>seconds</b>. Default: 10 (the underlying HTTP client default).</li>
19+
* <li>{@code readTimeout} – per-attempt response-read timeout in <b>seconds</b>.
20+
* Default: 10.</li>
21+
* <li>{@code writeTimeout} – per-attempt request-write timeout in <b>seconds</b>.
22+
* Default: 10.</li>
1723
* <li>{@code maxRetries} – retry attempts after the first failure (retries on HTTP
1824
* 408 / 429 / 5xx). Default: 0 — retries are OFF unless you set this (avoids auto-retrying non-idempotent writes).</li>
1925
* <li>{@code initialRetryDelayMillis} – base backoff before the first retry, in <b>milliseconds</b>.
@@ -22,6 +28,11 @@
2228
* <b>milliseconds</b>. Default: 2000.</li>
2329
* </ul>
2430
*
31+
* <p><b>How they relate:</b> {@code timeout} is the <i>total</i> ceiling for the whole call (all
32+
* attempts + backoff). {@code connectTimeout}/{@code readTimeout}/{@code writeTimeout} each bound a
33+
* single <i>phase of one attempt</i>; because they are per attempt, their sum across retries can
34+
* exceed {@code timeout}, but {@code timeout} always wins and cuts the call off.
35+
*
2536
* <p><b>Two levels + precedence:</b> set client-wide defaults on {@code Skyflow.builder()}, and/or
2637
* per-vault overrides on {@code VaultConfig}. The most specific value wins, resolved per field:
2738
* <b>per-vault &rarr; client-wide &rarr; SDK default</b>.
@@ -44,6 +55,9 @@ public static void main(String[] args) {
4455
// Per-vault overrides (optional). Any field left unset inherits the client-wide default below,
4556
// and then the SDK default.
4657
vaultConfig.setTimeout(30); // seconds – tighter overall ceiling for this vault
58+
vaultConfig.setConnectTimeout(5); // seconds – fail fast if the connection stalls
59+
vaultConfig.setReadTimeout(20); // seconds – allow a slower response read
60+
vaultConfig.setWriteTimeout(5); // seconds – bound the request write
4761
vaultConfig.setMaxRetries(2); // fewer retries for this vault
4862
vaultConfig.setInitialRetryDelayMillis(500L);
4963
vaultConfig.setMaxRetryDelayMillis(1000L);
@@ -53,6 +67,9 @@ public static void main(String[] args) {
5367
Skyflow skyflowClient = Skyflow.builder()
5468
.setLogLevel(LogLevel.ERROR)
5569
.timeout(60) // seconds – client-wide overall call timeout
70+
.connectTimeout(10) // seconds – client-wide per-attempt connect timeout
71+
.readTimeout(15) // seconds – client-wide per-attempt read timeout
72+
.writeTimeout(10) // seconds – client-wide per-attempt write timeout
5673
.maxRetries(3) // client-wide retry attempts
5774
.initialRetryDelayMillis(500L) // client-wide base backoff (ms)
5875
.maxRetryDelayMillis(2000L) // client-wide backoff cap (ms)

v3/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</parent>
1212

1313
<artifactId>skyflow-java</artifactId>
14-
<version>3.0.0-beta.12</version>
14+
<version>3.0.0-beta.12-dev.cad9a33</version>
1515
<packaging>jar</packaging>
1616
<name>${project.groupId}:${project.artifactId}</name>
1717
<description>Skyflow V3 SDK for the Java programming language</description>

v3/src/main/java/com/skyflow/Skyflow.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ public static final class SkyflowClientBuilder extends BaseSkyflowClientBuilder
5757
private final LinkedHashMap<String, VaultController> vaultClientsMap;
5858
// Client-wide HTTP config defaults (apply to all vaults unless a vault overrides). null => SDK default.
5959
private Integer timeout;
60+
private Integer connectTimeout;
61+
private Integer readTimeout;
62+
private Integer writeTimeout;
6063
private Integer maxRetries;
6164
private Long initialRetryDelayMillis;
6265
private Long maxRetryDelayMillis;
@@ -84,7 +87,8 @@ public SkyflowClientBuilder addVaultConfig(VaultConfig vaultConfig) throws Skyfl
8487
} else {
8588
this.vaultConfigMap.put(vaultConfigCopy.getVaultId(), vaultConfigCopy); // add new config in map
8689
VaultController controller = new VaultController(vaultConfigCopy, this.skyflowCredentials); // new controller with new config
87-
controller.setCommonHttpConfig(this.timeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis);
90+
controller.setCommonHttpConfig(this.timeout, this.connectTimeout, this.readTimeout,
91+
this.writeTimeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis);
8892
this.vaultClientsMap.put(vaultConfigCopy.getVaultId(), controller);
8993
LogUtil.printInfoLog(Utils.parameterizedString(
9094
InfoLogs.VAULT_CONTROLLER_INITIALIZED.getLog(), vaultConfigCopy.getVaultId()));
@@ -114,6 +118,39 @@ public SkyflowClientBuilder timeout(int timeout) {
114118
return this;
115119
}
116120

121+
/**
122+
* Client-wide per-attempt connection-establishment timeout in seconds. Applies to all vaults
123+
* unless a vault overrides it; when unset the underlying HTTP client default (10s) applies.
124+
* The overall {@code timeout} still bounds the whole call, including retries.
125+
*/
126+
public SkyflowClientBuilder connectTimeout(int connectTimeout) {
127+
this.connectTimeout = connectTimeout;
128+
propagateHttpConfig();
129+
return this;
130+
}
131+
132+
/**
133+
* Client-wide per-attempt response-read timeout in seconds. Applies to all vaults unless a
134+
* vault overrides it; when unset the underlying HTTP client default (10s) applies. The overall
135+
* {@code timeout} still bounds the whole call, including retries.
136+
*/
137+
public SkyflowClientBuilder readTimeout(int readTimeout) {
138+
this.readTimeout = readTimeout;
139+
propagateHttpConfig();
140+
return this;
141+
}
142+
143+
/**
144+
* Client-wide per-attempt request-write timeout in seconds. Applies to all vaults unless a
145+
* vault overrides it; when unset the underlying HTTP client default (10s) applies. The overall
146+
* {@code timeout} still bounds the whole call, including retries.
147+
*/
148+
public SkyflowClientBuilder writeTimeout(int writeTimeout) {
149+
this.writeTimeout = writeTimeout;
150+
propagateHttpConfig();
151+
return this;
152+
}
153+
117154
/** Client-wide retry attempt count. Applies to all vaults unless a vault overrides it. */
118155
public SkyflowClientBuilder maxRetries(int maxRetries) {
119156
this.maxRetries = maxRetries;
@@ -137,7 +174,8 @@ public SkyflowClientBuilder maxRetryDelayMillis(long maxRetryDelayMillis) {
137174

138175
private void propagateHttpConfig() {
139176
for (VaultController vault : this.vaultClientsMap.values()) {
140-
vault.setCommonHttpConfig(this.timeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis);
177+
vault.setCommonHttpConfig(this.timeout, this.connectTimeout, this.readTimeout,
178+
this.writeTimeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis);
141179
}
142180
}
143181

v3/src/main/java/com/skyflow/VaultClient.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public class VaultClient {
4949
private String currentVaultURL = null;
5050
// Client-wide (Skyflow builder) HTTP config defaults; null => fall back to the SDK defaults below.
5151
private Integer commonTimeout;
52+
private Integer commonConnectTimeout;
53+
private Integer commonReadTimeout;
54+
private Integer commonWriteTimeout;
5255
private Integer commonMaxRetries;
5356
private Long commonInitialRetryDelayMillis;
5457
private Long commonMaxRetryDelayMillis;
@@ -84,9 +87,13 @@ protected void setCommonCredentials(Credentials commonCredentials) throws Skyflo
8487
* Client-wide HTTP timeout/retry defaults from the Skyflow builder. Nulls out the cached client
8588
* so the next call rebuilds with the new values.
8689
*/
87-
protected void setCommonHttpConfig(Integer timeout, Integer maxRetries,
90+
protected void setCommonHttpConfig(Integer timeout, Integer connectTimeout, Integer readTimeout,
91+
Integer writeTimeout, Integer maxRetries,
8892
Long initialRetryDelayMillis, Long maxRetryDelayMillis) {
8993
this.commonTimeout = timeout;
94+
this.commonConnectTimeout = connectTimeout;
95+
this.commonReadTimeout = readTimeout;
96+
this.commonWriteTimeout = writeTimeout;
9097
this.commonMaxRetries = maxRetries;
9198
this.commonInitialRetryDelayMillis = initialRetryDelayMillis;
9299
this.commonMaxRetryDelayMillis = maxRetryDelayMillis;
@@ -175,8 +182,12 @@ protected void updateExecutorInHTTP() {
175182
vaultConfig.getInitialRetryDelayMillis(), commonInitialRetryDelayMillis, DEFAULT_INITIAL_RETRY_DELAY_MILLIS);
176183
long maxRetryDelayMillis = resolveLong(
177184
vaultConfig.getMaxRetryDelayMillis(), commonMaxRetryDelayMillis, DEFAULT_MAX_RETRY_DELAY_MILLIS);
185+
// Per-attempt timeouts: null => leave OkHttp's built-in 10s default (backward compatible).
186+
Integer connectTimeout = resolveNullableInt(vaultConfig.getConnectTimeout(), commonConnectTimeout);
187+
Integer readTimeout = resolveNullableInt(vaultConfig.getReadTimeout(), commonReadTimeout);
188+
Integer writeTimeout = resolveNullableInt(vaultConfig.getWriteTimeout(), commonWriteTimeout);
178189

179-
sharedHttpClient = new OkHttpClient.Builder()
190+
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder()
180191
.connectionPool(new ConnectionPool(10, 1, TimeUnit.MINUTES))
181192
.callTimeout(timeoutSeconds, TimeUnit.SECONDS) // overall ceiling; bounds the whole call incl. retries
182193
.addInterceptor(new RetryInterceptor( // OUTER: retries (Fern generated; jitter default 0.2)
@@ -186,8 +197,18 @@ protected void updateExecutorInHTTP() {
186197
.header("Authorization", "Bearer " + this.token)
187198
.build();
188199
return chain.proceed(requestWithAuth);
189-
})
190-
.build();
200+
});
201+
// Per-attempt phase timeouts; only override when explicitly configured.
202+
if (connectTimeout != null) {
203+
httpBuilder.connectTimeout(connectTimeout, TimeUnit.SECONDS);
204+
}
205+
if (readTimeout != null) {
206+
httpBuilder.readTimeout(readTimeout, TimeUnit.SECONDS);
207+
}
208+
if (writeTimeout != null) {
209+
httpBuilder.writeTimeout(writeTimeout, TimeUnit.SECONDS);
210+
}
211+
sharedHttpClient = httpBuilder.build();
191212
apiClientBuilder.httpClient(sharedHttpClient);
192213
}
193214
}
@@ -200,6 +221,17 @@ private static int resolveInt(Integer vaultLevel, Integer clientLevel, int defau
200221
return clientLevel != null ? clientLevel : defaultValue;
201222
}
202223

224+
/**
225+
* Resolve an optional int setting: vault-level override, else client-wide default, else null.
226+
* Null means "not configured" — the caller leaves the underlying HTTP client default in place.
227+
*/
228+
private static Integer resolveNullableInt(Integer vaultLevel, Integer clientLevel) {
229+
if (vaultLevel != null) {
230+
return vaultLevel;
231+
}
232+
return clientLevel;
233+
}
234+
203235
/** Resolve a long setting: vault-level override, else client-wide default, else SDK default. */
204236
private static long resolveLong(Long vaultLevel, Long clientLevel, long defaultValue) {
205237
if (vaultLevel != null) {

0 commit comments

Comments
 (0)