Skip to content

Commit 27446d8

Browse files
dfa1claude
andcommitted
fix(cli): close handle on all error paths, return Optional from openOnWorker
ViewCommand leaked the opened VortexHandle if anything threw between opening it and entering the grid-source try block (Sonar S2095, BLOCKER → reliability E): the close lived only in the inner try's finally. Wrap every post-open step in a try/finally that closes the handle on the worker thread. CliHandles.openOnWorker now returns Optional<VortexHandle> instead of null, and ViewCommand/TuiCommand branch on isEmpty(). The null sentinel made Sonar flag the missing-file guards as always-false (S2583) because it lost dataflow across the runAndAwait lambda; Optional both removes the smell and is idiomatic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d089308 commit 27446d8

3 files changed

Lines changed: 24 additions & 17 deletions

File tree

cli/src/main/java/io/github/dfa1/vortex/cli/CliHandles.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.net.URISyntaxException;
1111
import java.nio.file.Files;
1212
import java.nio.file.Path;
13+
import java.util.Optional;
1314
import java.util.concurrent.atomic.AtomicReference;
1415

1516
/// Shared handle plumbing for the interactive subcommands (`view`, `tui`).
@@ -27,10 +28,10 @@ private CliHandles() {
2728
///
2829
/// @param worker the I/O worker that owns the handle's arena
2930
/// @param target a local path or an `http(s)://` URL
30-
/// @return the opened handle, or `null` if the target is missing or malformed
31+
/// @return the opened handle, or empty if the target is missing or malformed
3132
/// @throws InterruptedException if interrupted while waiting on the worker
3233
/// @throws IOException if opening the file or URL fails
33-
static VortexHandle openOnWorker(IoWorker worker, String target)
34+
static Optional<VortexHandle> openOnWorker(IoWorker worker, String target)
3435
throws InterruptedException, IOException {
3536
AtomicReference<VortexHandle> handle = new AtomicReference<>();
3637
AtomicReference<IOException> failure = new AtomicReference<>();
@@ -44,7 +45,7 @@ static VortexHandle openOnWorker(IoWorker worker, String target)
4445
if (failure.get() != null) {
4546
throw failure.get();
4647
}
47-
return handle.get();
48+
return Optional.ofNullable(handle.get());
4849
}
4950

5051
/// Closes `handle` on the worker thread that opened it.

cli/src/main/java/io/github/dfa1/vortex/cli/TuiCommand.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.io.IOException;
99
import java.io.PrintStream;
10+
import java.util.Optional;
1011

1112
final class TuiCommand {
1213

@@ -19,10 +20,11 @@ static int run(String[] args) {
1920
return ExitStatus.USAGE_ERROR;
2021
}
2122
try (IoWorker worker = new IoWorker("vortex-tui-io")) {
22-
VortexHandle handle = CliHandles.openOnWorker(worker, args[1]);
23-
if (handle == null) {
23+
Optional<VortexHandle> opened = CliHandles.openOnWorker(worker, args[1]);
24+
if (opened.isEmpty()) {
2425
return ExitStatus.FILE_NOT_FOUND;
2526
}
27+
VortexHandle handle = opened.get();
2628
try {
2729
VortexInspectorTui.show(handle, worker, progressBar(System.err));
2830
} finally {

cli/src/main/java/io/github/dfa1/vortex/cli/ViewCommand.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.github.dfa1.vortex.reader.VortexHandle;
77

88
import java.io.IOException;
9+
import java.util.Optional;
910

1011
@SuppressWarnings("java:S106")
1112
final class ViewCommand {
@@ -22,24 +23,27 @@ static int run(String[] args) {
2223
System.err.print("Opening file... ");
2324
System.err.flush();
2425
long tOpen = System.nanoTime();
25-
VortexHandle handle = CliHandles.openOnWorker(worker, args[1]);
26-
if (handle == null) {
26+
Optional<VortexHandle> opened = CliHandles.openOnWorker(worker, args[1]);
27+
if (opened.isEmpty()) {
2728
return ExitStatus.FILE_NOT_FOUND;
2829
}
29-
System.err.println("done (" + (System.nanoTime() - tOpen) / 1_000_000L + " ms)");
30+
VortexHandle handle = opened.get();
31+
try {
32+
System.err.println("done (" + (System.nanoTime() - tOpen) / 1_000_000L + " ms)");
3033

31-
System.err.print("Indexing chunks... ");
32-
System.err.flush();
33-
long tIdx = System.nanoTime();
34-
try (LazyGridSource source = LazyGridSource.open(handle, worker)) {
35-
long ms = (System.nanoTime() - tIdx) / 1_000_000L;
36-
System.err.println("done — " + source.totalRows() + " rows × "
37-
+ source.columns().size() + " cols (" + ms + " ms)");
38-
VortexGridTui.show(args[1], source);
34+
System.err.print("Indexing chunks... ");
35+
System.err.flush();
36+
long tIdx = System.nanoTime();
37+
try (LazyGridSource source = LazyGridSource.open(handle, worker)) {
38+
long ms = (System.nanoTime() - tIdx) / 1_000_000L;
39+
System.err.println("done — " + source.totalRows() + " rows × "
40+
+ source.columns().size() + " cols (" + ms + " ms)");
41+
VortexGridTui.show(args[1], source);
42+
}
43+
return ExitStatus.OK;
3944
} finally {
4045
CliHandles.closeOnWorker(worker, handle);
4146
}
42-
return ExitStatus.OK;
4347
} catch (IOException | RuntimeException | InterruptedException e) {
4448
if (e instanceof InterruptedException) {
4549
Thread.currentThread().interrupt();

0 commit comments

Comments
 (0)