D-Dᴙum
D-Dᴙum

Reputation: 7890

InterruptedIOException using Spring-Shell TerminalUI Framework

Using JDK 17, Spring-Boot 3.3.0 and Spring-Shell 3.3.0 I'm experiencing ajava.io.IOError: java.io.InterruptedIOException: Command interrupted exception when the UI application is legitimately terminated by the user. It appears to be triggered when eventLoop.dispatch(ShellMessageBuilder.ofInterrupt()) is called.

Relevant code:

@ShellComponent
@Log4j2
@RequiredArgsConstructor
public class TerminalUICommands {
    private final TerminalUIBuilder tuiBuilder;

    @ShellMethod(key = {"app"})
    public void app() {
        final TerminalUI tui = this.tuiBuilder.build();
        final EventLoop eventLoop = tui.getEventLoop();

        eventLoop.onDestroy(eventLoop.keyEvents()
                .doOnNext(m -> {
                    if (m.getPlainKey() == KeyEvent.Key.q && m.hasCtrl()) {
                        eventLoop.dispatch(ShellMessageBuilder.ofInterrupt());
                    }
                })
                .subscribe());
        final BoxView mainView = this.createMainView(tui);
        final AppView appView = new AppView(
                mainView,
                this.createMenuView(tui, eventLoop),
                this.createStatusView(tui, eventLoop));
        tui.configure(appView);
        tui.setRoot(appView, true);
        tui.setFocus(mainView);
        tui.run();

    }

    private BoxView createMainView(final TerminalUI tui) {
        final BoxView view = new BoxView();
        tui.configure(view);
        return view;
    }

    private MenuBarView createMenuView(final TerminalUI tui, final EventLoop eventLoop) {
        final MenuBarView menuBarView = MenuBarView.of(
                MenuBarView.MenuBarItem.of("File", MenuView.MenuItem.of("Quit", MenuView.MenuItemCheckStyle.NOCHECK,
                        () -> eventLoop.dispatch(ShellMessageBuilder.ofInterrupt()))
                ));
        tui.configure(menuBarView);
        return menuBarView;
    }

    private StatusBarView createStatusView(final TerminalUI tui, final EventLoop eventLoop) {
        final StatusBarView statusBarView = new StatusBarView(List.of(
                StatusBarView.StatusItem.of("CTRL-Q Quit", () -> eventLoop.dispatch(ShellMessageBuilder.ofInterrupt()))
        ));
        tui.configure(statusBarView);
        return statusBarView;
    }
}
@EnableAsync
@SpringBootApplication
public class CustomShellApplication {

    public static void main(String[] args) {

        new SpringApplicationBuilder(CustomShellApplication.class)
                .web(WebApplicationType.NONE)
                        .run(args)
                                .close();
    }

}

Stack-trace:

java.io.IOError: java.io.InterruptedIOException: Command interrupted
    at org.jline.terminal.impl.AbstractPosixTerminal.getSize(AbstractPosixTerminal.java:65)
    at org.springframework.shell.component.view.TerminalUI.display(TerminalUI.java:262)
    at org.springframework.shell.component.view.TerminalUI.loop(TerminalUI.java:394)
    at org.springframework.shell.component.view.TerminalUI.run(TerminalUI.java:121)
    at com.XXXXXXXX.customshell.command.TerminalUICommands.app(TerminalUICommands.java:68)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.springframework.shell.command.invocation.InvocableShellMethod.doInvoke(InvocableShellMethod.java:306)
    at org.springframework.shell.command.invocation.InvocableShellMethod.invoke(InvocableShellMethod.java:232)
    at org.springframework.shell.command.CommandExecution$DefaultCommandExecution.evaluate(CommandExecution.java:230)
    at org.springframework.shell.Shell.evaluate(Shell.java:248)
    at org.springframework.shell.Shell.run(Shell.java:159)
    at org.springframework.shell.jline.InteractiveShellRunner.run(InteractiveShellRunner.java:72)
    at org.springframework.shell.DefaultShellApplicationRunner.run(DefaultShellApplicationRunner.java:66)
    at org.springframework.boot.SpringApplication.lambda$callRunner$4(SpringApplication.java:786)
    at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:83)
    at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60)
    at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:88)
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798)
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:786)
    at org.springframework.boot.SpringApplication.lambda$callRunners$3(SpringApplication.java:774)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
    at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:342)
    at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:149)
    at com.XXXXXXXX.customshell.CustomShellApplication.main(CustomShellApplication.java:16)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:91)
    at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:53)
    at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:58)
Caused by: java.io.InterruptedIOException: Command interrupted
    at org.jline.utils.ExecHelper.exec(ExecHelper.java:52)
    at org.jline.terminal.impl.exec.ExecPty.doGetConfig(ExecPty.java:177)
    at org.jline.terminal.impl.exec.ExecPty.getSize(ExecPty.java:171)
    at org.jline.terminal.impl.AbstractPosixTerminal.getSize(AbstractPosixTerminal.java:63)
    ... 41 common frames omitted
Caused by: java.lang.InterruptedException: null
    at java.base/java.lang.Object.wait(Native Method)
    at java.base/java.lang.Object.wait(Object.java:338)
    at java.base/java.lang.ProcessImpl.waitFor(ProcessImpl.java:434)
    at org.jline.utils.ExecHelper.waitAndCapture(ExecHelper.java:72)
    at org.jline.utils.ExecHelper.exec(ExecHelper.java:42)
    ... 44 common frames omitted

I also get the same behaviour if I extract the code from the Spring-Shell example here

Upvotes: 0

Views: 49

Answers (0)

Related Questions